Android 和 iOS 图书馆的行为就像在 1996 年之前从未有过夏令时一样

Android and iOS libraries behave as if there never was a daylight saving time before 1996

行话:

CET : Central European Time.
Daylight saving time : UTC+1 in winter, UTC+2 in summer.

在 CET 地区,Android 和 iOS 库的行为就像 1996 年之前从未有过夏令时一样,而 .Net 的行为一直存在。

为了说明这种行为,这里有一些用 .NET/Java 编写的代码,在 CET 机器上执行。

在 .Net 中:

static void PrintDate(String input)
{        
    String format = "yyyy-MM-ddTHH:mm:ss.fffffffzzz";         
    var date = DateTime.ParseExact(input, format, CultureInfo.InvariantCulture);
    var output = date.ToString(format, CultureInfo.InvariantCulture);
    System.Diagnostics.Debug.WriteLine(input + " => " + output);
}

在 Android 和 iOS 中(只是 java 示例,但两者的行为方式相同)。

static void printDate(String input)
{
    String format = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSZZZZZ";
    Date date =  new SimpleDateFormat(format).parse(input);
    String output = new SimpleDateFormat(format).format(date);
    Log.i("MyTag", input  + " => " + output);        
}

对方法的简单调用:

public static void Main()
{
    PrintDate("1993-10-06T00:00:00.0000000+02:00");
    PrintDate("1993-12-06T00:00:00.0000000+02:00");
    PrintDate("1996-10-06T00:00:00.0000000+02:00");
    PrintDate("1996-12-06T00:00:00.0000000+02:00");
}

这是 .Net 中的输出:

1993-10-06T00:00:00.0000000+02:00 => 1993-10-06T00:00:00.0000000+02:00
1993-12-06T00:00:00.0000000+02:00 => 1993-12-05T23:00:00.0000000+01:00
1996-10-06T00:00:00.0000000+02:00 => 1996-10-06T00:00:00.0000000+02:00
1996-12-06T00:00:00.0000000+02:00 => 1996-12-05T23:00:00.0000000+01:00

这是 Android/iOs

中的输出
1993-10-06T00:00:00.0000000+02:00 => 1993-10-05T23:00:00.0000000+01:00
1993-12-06T00:00:00.0000000+02:00 => 1993-12-05T23:00:00.0000000+01:00
1996-10-06T00:00:00.0000000+02:00 => 1996-10-06T00:00:00.0000000+02:00
1996-12-06T00:00:00.0000000+02:00 => 1996-12-05T23:00:00.0000000+01:00

我怎样才能使这三种平台形式之间的行为均匀化?

在 Java 中,如果您未在其中设置,SimpleDateFormat 将使用 JVM 默认时区。 (用 TimeZone.getDefault() 检查你的是什么)。

所以 1993-10-06T00:00+02:00 被转换为 1993-10-05T23:00+01:00 可能是因为默认时区是 1993 年 10 月有 +01:00 偏移的时区,而 1996 年 10 月是夏令时(+02:00).我的猜测是 Europe/Paris,但也可以是其他的,如 lots of timezones uses CET as a short name.

无论如何,只需检查 DST in Paris 的历史记录,并注意 1993 年 10 月的偏移量是 +01:00 而在 1996 年 10 月 6th 它是 +02:00。所以这是一个很好的猜测,但具有相同规则的任何时区都会有相同的行为。

此外,+02:00an offset, not a timezone. Just being +02:00 doesn't necessarily mean that it's CET during DST, because there's more than one timezone that uses this offset. And short names like CET are ambiguous and not standard, so you should consider using IANA timezones names(始终采用 Region/City 格式,如 America/Sao_PauloEurope/Paris)。

无论如何,如果你不想有可变的偏移量,你不应该依赖 JVM 默认时区,因为它可以有 DST 效果并且偏移量会根据日期(和默认时区 ).避免它的一种方法是在格式化程序中设置固定的 offset

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSZZZZZ");
// set the offset +02:00, so all dates will be formatted using this
// (instead of the current offset for the JVM default timezone)
sdf.setTimeZone(TimeZone.getTimeZone("GMT+02:00"));
Date date = sdf.parse("1993-10-06T00:00:00.0000000+02:00");
System.out.println(sdf.format(date));

只是一些简短的笔记:

  • SimpleDateFormat 不适用于小数点后超过 3 位的情况。在上面的例子中,它工作得很好,因为它全是零,但如果你有任何不同于零的值和超过 3 位数字,你可以有 strange, wrong, unexpected results。在这种情况下,您应该删除多余的数字,因为这个 class 根本无法处理超过 3 个(而且它也不适用于格式化)。
  • 我正在使用 JDK 7 进行测试,因此模式 ZZZZZ 不适用于解析。相反,我使用了 yyyy-MM-dd'T'HH:mm:ss.SSSSSSSXXX,它解析上面的输入,并将日期格式化为 1993-10-06T00:00:00.0000000+02:00(但请注意 X 在 JDK 6 中不可用)
  • 如果你想要另一个偏移量的输出,只需在getTimeZone方法中相应地改变它。如果你想要 UTC,请使用 getTimeZone("UTC")

Java新Date/TimeAPI

旧的 classes(DateCalendarSimpleDateFormat)有 lots of problems and design issues,它们正在被新的 APIs.

在Android中,可以使用ThreeTen Backport, a great backport for Java 8's new date/time classes. To make it work, you'll also need the ThreeTenABP (more on how to use it ).

这个新 API 的一个改进是支持纳秒(小数点后最多 9 位),因此它可以处理您的输入而不会出现 SimpleDateFormat.[=51 的问题=]

这个新的 API 还有 lots of new date/time types 适合不同的情况。在这种情况下,您有一个特定偏移量的日期和时间,并希望维护它。所以,最好的 class 是 org.threeten.bp.OffsetDateTime:

OffsetDateTime odt = OffsetDateTime.parse("1993-10-06T00:00:00.0000000+02:00");
System.out.println(odt.toString()); // 1993-10-06T00:00+02:00

请注意,如果秒和纳秒为零,则 toString() 方法会忽略它们。如果您希望输出与输入完全相同(小数点后有 7 位数字),只需创建一个 org.threeten.bp.format.DateTimeFormatter:

DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSXXX");
System.out.println(fmt.format(odt)); // 1993-10-06T00:00:00.0000000+02:00

要将其更改为另一个偏移量(或 UTC),请使用 org.threeten.bp.ZoneOffset

// convert to UTC
odt = odt.withOffsetSameInstant(ZoneOffset.UTC);
System.out.println(fmt.format(odt)); // 1993-10-05T22:00:00.0000000Z

// convert to another offset (+01:00)
odt = odt.withOffsetSameInstant(ZoneOffset.ofHours(1));
System.out.println(fmt.format(odt)); // 1993-10-05T23:00:00.0000000+01:00