如何获取一天的开始时间和结束时间?


121

如何获取一天的开始时间和结束时间?

像这样的代码是不正确的:

 private Date getStartOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int day = calendar.get(Calendar.DATE);
    calendar.set(year, month, day, 0, 0, 0);
    return calendar.getTime();
}

private Date getEndOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int day = calendar.get(Calendar.DATE);
    calendar.set(year, month, day, 23, 59, 59);
    return calendar.getTime();
}

它不精确到毫秒。


8
您的意思是“不准确”?
柯克·沃尔

代码有什么问题?您得到什么,意想不到的?您希望您的日子开始和结束于哪个时区?
马克·里德

4
上面的代码甚至没有使用传递给方法的日期。它应该在执行以下操作:创建日历后的calendar.setTime(date)。
Jerry Destremps

2
此问题假定日期时间值的分辨率为毫秒。对于java.util.Date和Joda-Time为True。但是对于Java 8中的java.time包(纳秒),某些数据库(例如Postgres(微秒),unix库(整秒))以及其他一些数据库,则为False 。请参阅我的答案以获取使用“半开式”的更安全方法。
罗勒·布尔克2014年

2
实际上,您应该获得第二天的开始时间,并且在当天迭代项目时(假设要这样做),您将使用<而不是<=来指定上限。那将排除第二天,但包括前一天的所有时间,直至纳秒。
Daniel F

Answers:


115

tl; dr

LocalDate                       // Represents an entire day, without time-of-day and without time zone.
.now(                           // Capture the current date.
    ZoneId.of( "Asia/Tokyo" )   // Returns a `ZoneId` object.
)                               // Returns a `LocalDate` object.
.atStartOfDay(                  // Determines the first moment of the day as seen on that date in that time zone. Not all days start at 00:00!
    ZoneId.of( "Asia/Tokyo" ) 
)                               // Returns a `ZonedDateTime` object.

一天的开始

获取在时区中看到的今天的完整长度。

使用Half-Open方法,其中开始是包含的,而结尾是排他的。这种方法解决了代码中无法解决一天中最后一秒的缺陷。

ZoneId zoneId = ZoneId.of( "Africa/Tunis" ) ;
LocalDate today = LocalDate.now( zoneId  ) ;

ZonedDateTime zdtStart = today.atStartOfDay( zoneId ) ;
ZonedDateTime zdtStop = today.plusDays( 1 ).atStartOfDay( zoneId ) ;

zdtStart.toString()= 2020-01-30T00:00 + 01:00 [非洲/突尼斯]

zdtStop.toString()= 2020-01-31T00:00 + 01:00 [非洲/突尼斯]

在UTC中看到相同的时刻。

Instant start = zdtStart.toInstant() ;
Instant stop = zdtStop.toInstant() ;

start.toString()= 2020-01-29T23:00:00Z

stop.toString()= 2020-01-30T23:00:00Z

如果要以UTC(而不是时区)显示日期的整天,请使用OffsetDateTime

LocalDate today = LocalDate.now( ZoneOffset.UTC  ) ;

OffsetDateTime odtStart = today.atTime( OffsetTime.MIN ) ;
OffsetDateTime odtStop = today.plusDays( 1 ).atTime( OffsetTime.MIN ) ;

odtStart.toString()= 2020-01-30T00:00 + 18:00

odtStop.toString()= 2020-01-31T00:00 + 18:00

这些OffsetDateTime 对象已经存在于UTC中,但是toInstant如果您需要这样的对象(根据定义始终位于UTC中),则可以调用。

Instant start = odtStart.toInstant() ;
Instant stop = odtStop.toInstant() ;

start.toString()= 2020-01-29T06:00:00Z

stop.toString()= 2020-01-30T06:00:00Z

提示:您可能有兴趣向项目中添加ThreeTen-Extra库,以使用其Interval类表示这对Instant对象。这个类提供了比较,如有用的方法abutsoverlapscontains,等等。

Interval interval = Interval.of( start , stop ) ;

interval.toString()= 2020-01-29T06:00:00Z / 2020-01-30T06:00:00Z

半开

mprivat答案是正确的。他的观点是不要试图获得一天的结束,而要与“第二天开始之前”进行比较。他的想法被称为“半开放式”方法,其中时间跨度的开始是包含在内的,结束是排除的

  • Java的当前日期时间框架(java.util.Date/Calendar和Joda-Time)都使用从纪元开始的毫秒数。但是在Java 8中,新的JSR 310 java.time。*类使用纳秒分辨率。如果切换到新类,则您基于强制一天中最后时刻的毫秒数编写的任何代码都是不正确的。
  • 如果其他来源使用其他分辨率,则比较它们的数据将出错。例如,Unix库通常使用整秒,而诸如Postgres之类的数据库将日期时间解析为微秒。
  • 某些夏令时更改会在午夜发生,这可能会使事情进一步混乱。

在此处输入图片说明

Joda-Time 2.3为此提供了一种方法,以获取一天的第一时间:withTimeAtStartOfDay()。同样在java.time中LocalDate::atStartOfDay

在StackOverflow中搜索“ joda半开”以查看更多讨论和示例。

请参阅这篇文章,时间间隔和其他范围应该是半开放的,由Bill Schneider发表。

避免使用旧的日期时间类

众所周知,java.util.Date和.Calendar类很麻烦。避免他们。

使用java.time类。该java.time框架是非常成功的正式继任者乔达时库。

java.time

java.time框架内置于Java 8及更高版本中。在ThreeTen- Backport项目中向后移植到Java 6和7,在ThreeTenABP项目中进一步适应了Android 。

一个Instant是在时间轴上的一个时刻UTC,分辨率为纳秒

Instant instant = Instant.now();

应用一个时区以获取当地时间的挂钟时间

ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );

要获得一天的第一刻,请遍历LocalDate该类及其atStartOfDay方法。

ZonedDateTime zdtStart = zdt.toLocalDate().atStartOfDay( zoneId );

使用半开方法,获取第二天的第一刻。

ZonedDateTime zdtTomorrowStart = zdtStart.plusDays( 1 );

Java中所有日期和时间类型的列表,包括现代和传统

当前,java.time框架缺少Interval如下面有关Joda-Time所述的类。但是,ThreeTen-Extra项目使用其他类扩展了java.time。该项目是将来可能向java.time添加内容的试验场。在其类中是IntervalInterval通过传递一对Instant对象来构造一个。我们可以InstantZonedDateTime对象中提取一个。

Interval today = Interval.of( zdtStart.toInstant() , zdtTomorrowStart.toInstant() );

关于java.time

java.time框架是建立在Java 8和更高版本。这些类取代麻烦的老传统日期时间类,如java.util.DateCalendar,和SimpleDateFormat

要了解更多信息,请参见Oracle教程。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310

现在处于维护模式Joda-Time项目建议迁移到java.time类。

您可以直接与数据库交换java.time对象。使用与JDBC 4.2或更高版本兼容的JDBC驱动程序。不需要字符串,不需要类。Hibernate 5和JPA 2.2支持java.timejava.sql.*

在哪里获取java.time类?


乔达时代

更新:Joda-Time项目现在处于维护模式,建议迁移到java.time类。我将保留本节的历史记录。

乔达时间有三个类来表示各种方式的时间跨度:IntervalPeriod,和Duration。一个Interval有特定的开始,宇宙的时间轴上结束。这符合我们代表“一天”的需要。

我们调用该方法,withTimeAtStartOfDay而不是将一天中的时间设置为零。由于夏令时和其他异常一天的第一时刻可能不是00:00:00

使用Joda-Time 2.3的示例代码。

DateTimeZone timeZone = DateTimeZone.forID( "America/Montreal" );
DateTime now = DateTime.now( timeZone );
DateTime todayStart = now.withTimeAtStartOfDay();
DateTime tomorrowStart = now.plusDays( 1 ).withTimeAtStartOfDay();
Interval today = new Interval( todayStart, tomorrowStart );

如果需要,可以将其转换为java.util.Date。

java.util.Date date = todayStart.toDate();

我需要使用LocalDateTime
user924

170

Java 8


public static Date atStartOfDay(Date date) {
    LocalDateTime localDateTime = dateToLocalDateTime(date);
    LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);
    return localDateTimeToDate(startOfDay);
}

public static Date atEndOfDay(Date date) {
    LocalDateTime localDateTime = dateToLocalDateTime(date);
    LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX);
    return localDateTimeToDate(endOfDay);
}

private static LocalDateTime dateToLocalDateTime(Date date) {
    return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
}

private static Date localDateTimeToDate(LocalDateTime localDateTime) {
    return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
}

更新:我添加了这2种方法,以我的Java实用工具类在这里

它在Maven中央存储库中,位于:

<dependency>
  <groupId>com.github.rkumsher</groupId>
  <artifactId>utils</artifactId>
  <version>1.3</version>
</dependency>

Java 7及更早版本


使用Apache Commons

public static Date atEndOfDay(Date date) {
    return DateUtils.addMilliseconds(DateUtils.ceiling(date, Calendar.DATE), -1);
}

public static Date atStartOfDay(Date date) {
    return DateUtils.truncate(date, Calendar.DATE);
}

没有Apache Commons

public Date atEndOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.set(Calendar.HOUR_OF_DAY, 23);
    calendar.set(Calendar.MINUTE, 59);
    calendar.set(Calendar.SECOND, 59);
    calendar.set(Calendar.MILLISECOND, 999);
    return calendar.getTime();
}

public Date atStartOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);
    return calendar.getTime();
}

5
我建议您不要使用这种方法,因为您会遇到时区,夏时制等时遇到的极端情况,像Joda这样的时库将为您解决这些问题。
Edward Anderson

7
我认为getStartOfDay应该是:LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);即LocalTime,而不是LocalDateTime。
康斯坦丁·库拉金

不要认为这些getEndOfDay(Date date)getStartOfDay(Date date)Java 8部分中的方法是正确的,即LocalDateTime.MAX和.MIN使您处于过去和将来的最早日期,而不是一天的开始和结束。相反,我建议使用.atStartOfDay()作为开始,建议使用.plusDays(1).atStartOfDay()。minusNanos(1)作为结束。
Glen Mazza

方法localDateTimeToDate(LocalDateTime startOfDay)抛出异常
-java.lang.IllegalArgumentException

1
@GlenMazza了Java 8解决方案使用本地时间。最大,不LocalDateTime.MAX。解决方法是正确的。
弗雷德里科·潘图扎

28

在getEndOfDay中,您可以添加:

calendar.set(Calendar.MILLISECOND, 999);

尽管从数学上讲,您只能指定一天的结束时间,而只能说是“第二天的开始之前”。

因此,不用说,if(date >= getStartOfDay(today) && date <= getEndOfDay(today))您应该说:if(date >= getStartOfDay(today) && date < getStartOfDay(tomorrow))。这是一个更加可靠的定义(并且您不必担心毫秒精度)。


1
答案的后半部分是最好的方法。这种方法称为“半开放式”。我在回答中进一步解释。
罗勒·布尔克

14

java.time

使用java.timeJava 8中内置的框架。

import java.time.LocalTime;
import java.time.LocalDateTime;

LocalDateTime now = LocalDateTime.now(); // 2015-11-19T19:42:19.224
// start of a day
now.with(LocalTime.MIN); // 2015-11-19T00:00
now.with(LocalTime.MIDNIGHT); // 2015-11-19T00:00
// end of a day
now.with(LocalTime.MAX); // 2015-11-19T23:59:59.999999999

3
该答案忽略了诸如夏令时(DST)之类的异常。这些Local…课程没有时区和针对异常进行调整的概念。例如,某些时区的DST转换是午夜,因此一天的第一时刻是该时间01:00:00.0(凌晨1点),而不是00:00:00.0此代码产生的值。使用ZonedDateTime代替。
罗勒·布尔克

4

使用java8 java.time.ZonedDateTime查找一天开始的另一种方法,而不是LocalDateTime简单地将输入ZonedDateTime截断为DAYS:

zonedDateTimeInstance.truncatedTo( ChronoUnit.DAYS );

2

对于Java 8,以下单行语句有效。在此示例中,我使用UTC时区。请考虑更改您当前使用的TimeZone。

System.out.println(new Date());

final LocalDateTime endOfDay       = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
final Date          endOfDayAsDate = Date.from(endOfDay.toInstant(ZoneOffset.UTC));

System.out.println(endOfDayAsDate);

final LocalDateTime startOfDay       = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
final Date          startOfDayAsDate = Date.from(startOfDay.toInstant(ZoneOffset.UTC));

System.out.println(startOfDayAsDate);

如果与输出没有时差。尝试:ZoneOffset.ofHours(0)


1
这如何增加现有答案之外的价值?此外,您忽略了时区这一关键问题。并且将UTC常量传递给toInstant非法语法和概念上是多余的。
罗勒·布尔克

@BasilBourque“这如何增加现有答案之外的价值?” 这就是stackoverflow的工作方式。顺便说一句,这只是一个模板。人们可以改变他们想要的方式。将UTC传递给instant是完全合法的,您可以检查输出。
的Firat KUCUK

2

Java 8或ThreeTenABP

ZonedDateTime

ZonedDateTime curDate = ZonedDateTime.now();

public ZonedDateTime startOfDay() {
    return curDate
    .toLocalDate()
    .atStartOfDay()
    .atZone(curDate.getZone())
    .withEarlierOffsetAtOverlap();
}

public ZonedDateTime endOfDay() {

    ZonedDateTime startOfTomorrow =
        curDate
        .toLocalDate()
        .plusDays(1)
        .atStartOfDay()
        .atZone(curDate.getZone())
        .withEarlierOffsetAtOverlap();

    return startOfTomorrow.minusSeconds(1);
}

// based on https://stackoverflow.com/a/29145886/1658268

LocalDateTime

LocalDateTime curDate = LocalDateTime.now();

public LocalDateTime startOfDay() {
    return curDate.atStartOfDay();
}

public LocalDateTime endOfDay() {
    return startOfTomorrow.atTime(LocalTime.MAX);  //23:59:59.999999999;
}

// based on https://stackoverflow.com/a/36408726/1658268

希望对您有所帮助。


“ atTime”方法是一个很好的方法,可以在一天结束时使用常量“ LocalTime.MAX”。真好!
安德森先生

2

我尝试了这段代码,它运行良好!

final ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
final ZonedDateTime startofDay =
    now.toLocalDate().atStartOfDay(ZoneOffset.UTC);
final ZonedDateTime endOfDay =
    now.toLocalDate().atTime(LocalTime.MAX).atZone(ZoneOffset.UTC);

如果您拥有ZonedDateTimeInstant可以支配,则无需使用java.sql.Timestamp。该类是现在被java.time类取代的可怕的旧式旧类之一。从JDBC 4.2开始,我们可以直接与数据库交换java.time对象。您混合使用旧式和现代类对我来说毫无意义。
罗勒·布尔克

1

不依赖于任何框架的另一个解决方案是:

static public Date getStartOfADay(Date day) {
    final long oneDayInMillis = 24 * 60 * 60 * 1000;
    return new Date(day.getTime() / oneDayInMillis * oneDayInMillis);
}

static public Date getEndOfADay(Date day) {
    final long oneDayInMillis = 24 * 60 * 60 * 1000;
    return new Date((day.getTime() / oneDayInMillis + 1) * oneDayInMillis - 1);
}

请注意,它返回基于UTC的时间


1
private Date getStartOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int day = calendar.get(Calendar.DATE);
    calendar.setTimeInMillis(0);
    calendar.set(year, month, day, 0, 0, 0);
    return calendar.getTime();
    }

private Date getEndOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int day = calendar.get(Calendar.DATE);
    calendar.setTimeInMillis(0);
    calendar.set(year, month, day, 23, 59, 59);
    return calendar.getTime();
    }

calendar.setTimeInMillis(0); 使您的精度达到毫秒


1
这些可怕的日期时间类被年前由现代取代java.time在JSR 310中定义的类
罗勒布尔克

@BasilBourque包java.time仅出现在Java 8中,该Java仅在Android N(API 26)之后可用
ivan8m8

@ ivan8m8大多数Java.time功能都在ThreeTen-Backport库中反向移植到Java 6和7 。在ThreeTenABP库中进一步适用于早期的Android(<26)。
罗勒·布尔克

@BasilBourque,但是ThreeTen 在Android上使用了效率极低的机制
ivan8m8

@ ivan8m8你可能会前身的思维java.time乔达时间。再次,请参见ThreeTenABP项目。我还没有听说过使用该库的Android效率低下。
罗勒·布尔克

0

我知道有些晚了,但是对于Java 8,如果您使用的是OffsetDateTime(它具有很多优势,例如TimeZone,Nanoseconds等),则可以使用以下代码:

OffsetDateTime reallyEndOfDay = someDay.withHour(23).withMinute(59).withSecond(59).withNano(999999999);
// output: 2019-01-10T23:59:59.999999999Z

0

我对所有解决方案都有一些不便之处,因为我需要Instant变量的类型,并且时区总是干扰更改所有内容,然后结合解决方案,我发现这是一个不错的选择。

        LocalDate today = LocalDate.now();
        Instant startDate = Instant.parse(today.toString()+"T00:00:00Z");
        Instant endDate = Instant.parse(today.toString()+"T23:59:59Z");

结果是

        startDate = 2020-01-30T00:00:00Z
        endDate = 2020-01-30T23:59:59Z

希望对您有帮助


这一天实际上是在第一时刻的到来结束之后的一天。您的代码会跳过一天的最后一整秒,这是十亿分之一秒,从未报告过。请参阅我的答案,以了解定义时间跨度的半开放式方法。
罗勒·布尔克

将字符串作为一种处理日期时间值的方法通常是一种较差的方法。使用智能对象而不是哑字符串来完成这项工作。该java.time班给你需要为这个问题而不是诉诸字符串操作的所有功能。
罗勒·布尔克

我刚刚在我的答案中添加了tl; dr部分,其中显示了如何将一天的范围作为一对对象。提示:您可能会对将ThreeTen-Extra库添加到项目中以使用其类感兴趣。InstantInterval
罗勒·布尔克

0

我认为最简单的方法是:

// Joda Time

DateTime dateTime=new DateTime(); 

StartOfDayMillis = dateTime.withMillis(System.currentTimeMillis()).withTimeAtStartOfDay().getMillis();
EndOfDayMillis = dateTime.withMillis(StartOfDayMillis).plusDays(1).minusSeconds(1).getMillis();

然后可以根据您对Joda Time的要求,将这些毫秒数转换为Calendar,Instant或LocalDate。


-2
public static Date beginOfDay(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);

    return cal.getTime();
}

public static Date endOfDay(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.set(Calendar.HOUR_OF_DAY, 23);
    cal.set(Calendar.MINUTE, 59);
    cal.set(Calendar.SECOND, 59);
    cal.set(Calendar.MILLISECOND, 999);

    return cal.getTime();
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.