Date 是否可以从表示本地午夜的UTC时间计算本地日期?

Date 是否可以从表示本地午夜的UTC时间计算本地日期?,date,datetime,timezone,utc,timezone-offset,Date,Datetime,Timezone,Utc,Timezone Offset,假设您在给定的时区(如美国/纽约)中选择一个本地日期(如2016-12-28),并将该日期的开始转换为UTC(在本例中为2016-12-28T05:00:00Z) 那么,是否有可能从UTC时间返回原始本地日期,而不知道时区?您只知道UTC时间代表某个本地午夜/一天的开始 我认为这在某些情况下是可能的,例如,当偏移量相当小时,但我不确定当时区接近日期线时,不会有两个可能的答案,即当两个时区具有相同的时间,但不同的日期/偏移量(-10和+14) (这个问题最初是在一个数据库中遇到的,该数据库中的本地

假设您在给定的时区(如美国/纽约)中选择一个本地日期(如2016-12-28),并将该日期的开始转换为UTC(在本例中为2016-12-28T05:00:00Z)

那么,是否有可能从UTC时间返回原始本地日期,而不知道时区?您只知道UTC时间代表某个本地午夜/一天的开始

我认为这在某些情况下是可能的,例如,当偏移量相当小时,但我不确定当时区接近日期线时,不会有两个可能的答案,即当两个时区具有相同的时间,但不同的日期/偏移量(-10和+14)

(这个问题最初是在一个数据库中遇到的,该数据库中的本地日期错误地存储在UTC中,并且很难再次检索原始时区数据。)

使用此方法,我可以计算出这个问题的可能时区列表

更新我为这段代码重新编写了驱动程序,以真正探索一整天的结果,并将其与Matt优秀答案中的具体示例进行了比较

代码如下:

#include <iostream>
#include <vector>

template <class Duration>
std::vector<date::zoned_time<std::common_type_t<Duration, std::chrono::seconds>>>
find_by_offset(date::sys_time<Duration> tp, const std::chrono::seconds& offset)
{
    using namespace std::chrono;
    using namespace date;
    std::vector<zoned_time<std::common_type_t<Duration, std::chrono::seconds>>> results;
    auto& db = get_tzdb();
    for (auto& z : db.zones)
    {
        if (z.get_info(tp).offset == offset)
            results.push_back(make_zoned(&z, tp));
    }
    return results;
}

int
main()
{
    using namespace date;
    using namespace std::chrono;
    for (auto offset = -15h; offset <= 13h; offset += 1h)
    {
        auto tp = sys_days{2016_y/12/28} + offset;
        std::cout << "These are all the timezones it is midnight at " << format("%F %T %Z\n", tp);
        auto d0 = round<days>(tp);
        auto dm1 = d0 - days{1};
        auto dp1 = d0 + days{1};
        auto v = find_by_offset(tp, dm1 - tp);
        for (auto const& zt : v)
            std::cout << format("%F %T %Z %z ", zt) << zt.get_time_zone()->name() << '\n';
        v = find_by_offset(tp, d0 - tp);
        for (auto const& zt : v)
            std::cout << format("%F %T %Z %z ", zt) << zt.get_time_zone()->name() << '\n';
        v = find_by_offset(tp, dp1 - tp);
        for (auto const& zt : v)
            std::cout << format("%F %T %Z %z ", zt) << zt.get_time_zone()->name() << '\n';
        std::cout << '\n';
    }
}
注:

  • 2016-12-28 10:00:00 UTC在其结果中列出了太平洋/檀香山和太平洋/汤加塔普(以及其他几个)

  • 这些结果列出了所有
    -10/+14
    -11/+13
    配对,以及其他一些配对,但这些少数配对只涉及“海上航行”时区,如“Etc/GMT+12”

  • 如果您更改计划以探索2016-10-16,
    美国/圣保罗
    从未列出,尽管它在2016-12-28上市,因为2016-10-16美国/圣保罗没有午夜。但您确实可以在2016-10-16 03:00:00 UTC(偏移量-0300)下找到
    America/Bahia

  • 更改计划以探索2016-11-06显示,
    America/Havana
    在2016-11-06 04:00:00 UTC和2016-11-06 05:00:00 UTC下列出

  • 在这里强调的示例中,没有一个时区是给定UTC时间点的午夜。对于UTC偏移量不是整小时数的时区,可能存在这样一个时间点

啊,是的,这里有一些:

These are all the timezones it is midnight at 2016-12-27 09:30:00 UTC
2016-12-27 00:00:00 MART -0930 Pacific/Marquesas

These are all the timezones it is midnight at 2016-12-27 10:15:00 UTC
2016-12-28 00:00:00 CHADT +1345 Pacific/Chatham

These are all the timezones it is midnight at 2016-12-27 14:30:00 UTC
2016-12-28 00:00:00 ACST +0930 Australia/Darwin

These are all the timezones it is midnight at 2016-12-27 15:15:00 UTC
2016-12-28 00:00:00 ACWST +0845 Australia/Eucla

These are all the timezones it is midnight at 2016-12-27 15:30:00 UTC
2016-12-28 00:00:00 KST +0830 Asia/Pyongyang

These are all the timezones it is midnight at 2016-12-27 18:15:00 UTC
2016-12-28 00:00:00 NPT +0545 Asia/Kathmandu

These are all the timezones it is midnight at 2016-12-27 19:30:00 UTC
2016-12-28 00:00:00 AFT +0430 Asia/Kabul

These are all the timezones it is midnight at 2016-12-27 20:30:00 UTC
2016-12-28 00:00:00 IRST +0330 Asia/Tehran

These are all the timezones it is midnight at 2016-12-28 03:30:00 UTC
2016-12-28 00:00:00 NST -0330 America/St_Johns

在某些限制条件下,可以识别时区偏移(
UTC-05:00
),但不能识别原始时区(
America/New_York
)。正如霍华德在他的回答中所显示的那样,你只能列出当时偏移量可能属于的时区。还有其他使此问题变得困难的边缘情况:

  • 您给出了一个非常清楚的例子,说明了如何无法确定接近国际日期线的偏移的日期

      < P>例如,考虑<代码> 2016—1231 T10:00:这可能是
      2016-12-31T00:00:00-10:00
      (可能是
      Pacific/Honolu
      ),也可能是
      2017-01-01T00:00:00+14:00
      (可能是
      Pacific/Tongatapu

    • -10/+14
      -11/+13
      配对都是可能的,但地球上没有任何有人居住的地方实际使用
      -12
      。因此,如果您的值正好在中午,那么它们很可能是
      +12
      ,除非您处理的是海上船舶

  • 您期望的时区中可能不存在本地午夜值

    • 例如,
      2016-10-16T03:00:00Z
      美国/圣保罗
      美国/巴伊亚
      都是一天的开始。然而,
      美国/圣保罗的当地时间是
      01:00
      ,而不是
      00:00
      。由于DST提前转换,那天没有午夜
  • 本地午夜值在您期望的时区中可能存在两次

    • 例如,在
      美国/哈瓦那
      2016-11-06T04:00:00Z
      2016-11-06T05:00:00Z
      都有一个当地时间
      00:00
      ,这是由于它们的DST回退转换
因此,在一般情况下,您可能能够将大部分偏移量解析回其原始偏移量,但对于偏移量为
-10
-11
+13
+14
的时区,以及在午夜(春季)或凌晨1:00(秋季)进行DST转换的时区,您将存在歧义。(请记住,北半球和南半球的春季和秋季是不同的。)

答案是正确的

  • 如果您确定数据库中存储的时刻表示某个时区某处一天中的第一个时刻,则可以获取日期
  • 您可以猜测时区,但无法确定当多个时区共享UTC偏移量时的原始时区
java.time 下面是一些使用现代Java.time类的Java代码

假设您在给定的时区(如美国/纽约)中选择一个本地日期(如2016-12-28),并将该日期的开始转换为UTC(在本例中为2016-12-28T05:00:00Z)

注意,我们让java.time通过
LocalDate::atStartOfDay
方法确定一天中的第一个时刻。不要假设一天从00:00:00开始。夏时制等异常情况意味着一天可能在其他时间开始,如01:00:00

LocalDate localDate = LocalDate.parse( "2016-12-28" ) ;
ZoneId zNewYork = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtNewYork = localDate.atStartOfDay( zNewYork ) ;   // First moment of the day in that zone on that date.
通过提取一个
瞬间
来调整UTC值。根据定义,
即时
总是以UTC为单位

Instant instant = zdtNewYork.toInstant() ;
在数据库中以类似于SQL标准
带时区的时间戳
类型的列存储

myPreparedStatement.setObject( … , instant ) ;
在不知道时区的情况下返回原始的本地日期

找回

Instant instant = myResultSet.getObject( … , Instant.class ) ;
现在对每个时区进行实验。仅供参考,尽管该页面可能已过时

对于每个区域,将我们的
瞬间
(我们的UTC时刻)调整到该区域,以获得
区域数据
Instant instant = myResultSet.getObject( … , Instant.class ) ;
List< ZoneId > hits = new ArrayList<>() ;
LocalDate originalLocalDate = null ;
Set< String > zoneIds = ZoneId.getAvailableZoneIds() ;  // Gets the set of available zone IDs.
for( String zoneId : zoneIds ) {
    ZoneId z = ZoneId.of( zoneId ) ;                        // Get zone with that name.
    ZonedDateTime zdt = instant.atZone( z ) ;
    LocalDate ld = zdt.toLocalDate() ;                      // Extract the date-only value, dropping the time-of-day and dropping the time zone.
    ZonedDateTime startOfDay = ld.atStartOfDay( z ) ;       // Determine first moment of the day on that date in that zone.
    Instant instantOfStartOfDay = startOfDay.toInstant() ;  // Adjust back to UTC.
    boolean hit = instant.equals( instantOfStartOfDay ) ;
    if( hit ) {
        originalLocalDate = ld ;
        hits.add( z ) ;  // Collect this time zone as the zone possibly used originally.
    }
}