【问题标题】:ScheduledExecutorService execute every night at 12 AM UTC TimeScheduledExecutorService 每晚在 UTC 时间凌晨 12 点执行
【发布时间】:2021-03-30 01:48:29
【问题描述】:

我想每天凌晨 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
);

【问题讨论】:

    标签: java datetime runnable scheduledexecutorservice


    【解决方案1】:

    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 是您想要的 UTC。调用日期时间方法时省略可选的时区参数。这种遗漏意味着 JVM 当前的默认时区在运行时被隐式和静默地应用。该默认值可能随时更改。该 JVM 中任何应用程序的任何线程中的任何代码都可以在运行时更改默认值(!)。

    不要隐式依赖 JVM 当前的默认时区,始终明确指定所需/预期的时区。在您的情况下,我们需要ZoneOffset.UTC。不要假设/希望部署 JVM 的当前默认设置为 UTC,并保持 UTC,而是使用 constant 明确指定。

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

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

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

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

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

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

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

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

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

    计算从现在到明天的第一刻之间经过的时间。 Duration 类表示这样的时间跨度与时间轴无关。

    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的对象是什么。因此,在您发布更多信息之前,这是我们无法提供帮助的代码的主要部分。我担心你把它命名为“线程”。该对象必须是ScheduledExecutorService 的实现,而不是线程。

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


    关于java.time

    java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

    Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

    要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

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

    从哪里获得 java.time 类?

    • Java SE 8Java SE 9 及更高版本
      • 内置。
      • 标准 Java API 的一部分,带有捆绑实现。
      • Java 9 添加了一些小功能和修复。
    • Java SE 6Java SE 7
      • ThreeTen-Backport 中的大部分 java.time 功能都向后移植到 Java 6 和 7。
    • Android
      • java.time 类的 Android 捆绑包实现的更高版本。
      • 对于早期的 Android (ThreeTenABP 项目采用 ThreeTen-Backport(如上所述)。见How to use ThreeTenABP…

    ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

    【讨论】:

    • 嗨,是的,我的服务器时间始终为 UTC,稍后我会检查您的更改,但请检查我的代码,并根据我在问题中提到的要求让我知道我的代码是否正确
    • 我试图警告您,您应该假设您当前的默认时区将始终为 UTC。这是您作为程序员无法控制的事实。没有必要依赖当前的默认区域,那么为什么要冒险呢?只需按照我的代码所示传递ZoneOffset.UTC,您就可以少担心一个问题。您说要求是在 UTC 午夜运行,并且不指定时区是我认为出错的最大风险。
    • 好的,让我检查一下
    • @basil ,在我看来,在 12:00 第一次执行后,如果您每 24 小时重新安排一次并让服务运行,则安排将关闭一小时(挂钟时间)当夏令时发生时。例如该任务将在每天 11:00 或 13:00 运行,直到 DST 再次更改。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-29
    • 2017-12-12
    • 1970-01-01
    • 2020-04-13
    • 2015-12-29
    • 1970-01-01
    相关资源
    最近更新 更多