ScheduledExecutorService 在 UTC 时间每晚 12 点执行

ScheduledExecutorService execute every night at 12 AM UTC Time

我想每天凌晨 12 点准时启动 ScheduledExecutorService,计划必须在今天 22/02/2017 00:00:00(UTC 时间)开始,谁能告诉我我的代码是否正确?

DateTime today = new DateTime().withTimeAtStartOfDay(); 
DateTime startOfTommorrow = today.plusDays(1).withTimeAtStartOfDay();

Long midnight = startOfTommorrow.getMillis();
long midnights = (midnight / 1000)  / 60;
final DateFormat nextDateTymFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

System.out.println("***********************************");
System.out.println("Schedule Updater "+nextDateTymFormat.format(new Date()));
System.out.println("today "+today);
System.out.println("startOfTommorrow "+startOfTommorrow);
System.out.println("midnight Long "+midnight);
System.out.println("***********************************");
vitalScheduleThread.scheduleAtFixedRate(new Runnable() {

    @Override
    public void run() {

        System.out.println("Hello vitalSchService !!"+nextDateTymFormat.format(new Date()));

        Thread.currentThread().setName("vitalSchService");

        //sendMail();
        vitalSchedule.process(springContext);
    }
}, midnight , 86400000 , TimeUnit.MILLISECONDS
);

tl;博士

OffsetDateTime now = OffsetDateTime.now( ZoneOffset.UTC ) ;  // Capture the current moment.

….scheduleAtFixedRate(
    new Runnable() { … } ,           // Define task to be executed as a `Runnable`.
    Duration.between(                // Determine amount of time for initial delay until first execution of our Runnable.
        now ,                        // Current moment.
        now.toLocalDate().plusDays( 1 ).atStartOfDay( ZoneOffset.UTC )  // Determine the first moment of tomorrow in our target time zone (UTC). Used as the exclusive end of our Half-Open span of time.
    ) ,
    TimeUnit.DAYS.toMillis( 1 ) ,    // Amount of time between subsequent executions of our Runnable. Use self-documenting code rather than a “magic number” such as `86400000`. 
    TimeUnit.MILLISECONDS            // Specify the granularity of time used in previous pair of arguments.
)                                    // Returns a `ScheduledFuture` which you may want to cache.

详情

明确指定区域

您假设 JVM 当前为 time zone is your desired UTC。调用日期时间方法时,您可以省略可选的时区参数。该遗漏意味着 JVM 当前的默认时区在 运行 时间隐式且静默地应用。该默认设置可能随时更改。该 JVM 中任何应用程序的任何线程中的任何代码都可以在 运行time(!) 期间更改默认值。

不是隐式依赖 JVM 当前的默认时区,始终明确指定您的 desired/expected 时区。对于您的情况,我们需要 ZoneOffset.UTC. Instead of assuming/hoping the deployment JVM’s current default is set to UTC, and stays at UTC, specify explicitly using the constant.

您似乎在使用优秀的 Joda-Time 库。该项目现在处于维护模式,团队建议迁移到 java.time classes。相同的基本概念,受 Joda-Time 启发 java.time.

首先获取 UTC 中看到的当前时刻。

OffsetDateTime now = OffsetDateTime.now( ZoneOffset.UTC );

从中提取一个仅限日期的值。加一得到明天的日期。

LocalDate today = now.toLocalDate();
LocalDate tomorrow = today.plusDays( 1 );

“午夜”一词可能含糊不清。相反,关注“一天中的第一刻”的概念。

我们的目标是在您第一次执行执行程序服务之前延迟的时间。所以我们需要从现在到明天第一刻的时间跨度。

并且在确定时间跨度时,当开始包含而结束不包含时,使用半开法。所以我们的时间跨度从现在(当前时刻)开始,运行s 直到,但不包括)明天的第一个时刻。

让java.time决定明天的第一时刻。在 UTC 中,一天总是从 00:00 开始。但在某些日期的某些时区并非如此,一天可能会在 01:00 这样的时间开始。因此,作为一种习惯,总是让 java.time 确定一天中的第一时刻。 OffsetDateTime 明天开始 = OffsetDateTime.of( 明天 , LocalTime.MIN , ZoneOffset.UTC );

计算从现在到明天的第一刻所经过的时间。 Duration class 表示独立于时间轴的时间跨度。

Duration d = Duration.between( now ,  tomorrowStart );
long millisUntilTomorrowStart = d.toMillis();

不要使用神秘的数字文字,例如 86400000,而是使用自记录调用。

TimeUnit.DAYS.toMillis( 1 )

因此您的 ScheduledExecutorService 调用将如下所示:

….scheduleAtFixedRate(
    new Runnable() { … } ,          // Task to be executed repeatedly, defined as a Runnable.
    millisUntilTomorrowStart ,      // Initial delay, before first execution. Use this to get close to first moment of tomorrow in UTC per our code above.
    TimeUnit.DAYS.toMillis( 1 ) ,   // Amount of time in each interval, between subsequent executions of our Runnable.
    TimeUnit.MILLISECONDS           // Unit of time intended by the numbers in previous two arguments.
)

整日递增,不需要毫秒这样细的粒度。由于各种原因,执行者没有 运行 把握好时机。所以我可能会在几分钟内计算出来。但并不重要。

非常重要:您需要将 Runnable 的 run 方法的代码包含在任何异常的陷阱中。如果任何类型的异常到达执行器,执行器都会静默停止。没有进一步的任务调度,也没有警告。搜索 Stack Overflow 以获取更多信息,包括我的回答。

你没有解释你调用的对象是什么scheduleAtFixedRate。因此,在您 post 更多信息之前,这是我们无法提供帮助的代码的主要部分。我担心你将它命名为“Thread”。该对象必须是 ScheduledExecutorService 的实现,而不是线程。

提示:避免运行在午夜整点做事。许多事情往往会在午夜在计算机上发生。例如,闰秒调整、许多 Unix 清理实用程序以及天真的管理员可能已安排的例行活动(例如备份)。等待五到十五分钟可能会避免麻烦和神秘的问题。


关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

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

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

您可以直接与您的数据库交换 java.time 对象。使用 JDBC driver compliant with JDBC 4.2 或更高版本。不需要字符串,不需要 java.sql.* classes.

在哪里获取java.time classes?

  • Java SE 8, Java SE 9,及以后
    • 内置。
    • 标准 Java API 的一部分,带有捆绑实施。
    • Java 9 添加了一些小功能和修复。
  • Java SE 6 and Java SE 7
  • Android
    • Android java.time classes.
    • 捆绑实施的更高版本
    • 对于较早的 Android (<26),ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See .

ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.