SimpleDateFormat显示错误的本地时间

问题描述

我想将字符串与当前时间和日期一起存储到Android应用程序的数据库(SQLite)中。为此,我正在使用SimpleDateFormat。不幸的是,它没有显示正确的时间。我尝试了两种选择。

第一个选项(来自SimpleDateFormat with TimeZone

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z",Locale.getDefault());
        sdf.format(new Date());

第二个选项(来自Java SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") gives timezone as IST

    SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss'Z'");
    sdf2.setTimeZone(TimeZone.getTimeZone("CEST"));

在两种情况下,时间都是错误的。我的笔记本电脑或手机显示的不是本地时间,但输出时间早了2个小时。我该如何改变?我想在计算机上同时显示柏林的当前时间(CEST)。我感谢所有评论。

解决方法

ISO 8601

假设您的SQLite没有datetime数据类型,我建议您使用国际标准ISO 8601格式将日期时间作为字符串存储到SQLite。接下来,考虑使用java.time(现代的Java日期和时间API)进行日期和时间工作。这两个建议很好地结合在一起。常见的建议是将日期和时间存储在UTC上,但我知道您更喜欢欧洲/柏林的时间。

    ZoneId databaseTimeZone = ZoneId.of("Europe/Berlin");
    
    ZonedDateTime now = ZonedDateTime.now(databaseTimeZone);
    String databaseTime = now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
    
    System.out.println(databaseTime);

刚刚运行时从上面的输出:

2020-09-27T15:54:21.53 + 02:00

我故意将UTC的偏移量包含在字符串中。这样,任何检索字符串的人都可以将时间转换为UTC或他们喜欢的时区。如果用户前往印度看泰姬陵并在那里获取数据,则转换为印度标准时间没问题。当柏林从夏令时(DST)更改为标准时间并且重复相同的时钟时间时,偏移量还可以消除10月夜间的时间。更改前的时间偏移+02:00,更改后的时间偏移+01:00。

如何更改格式(?)

编辑:如果您坚持使用自己的格式来获取信息和人类可读性,请为此构建一个格式化程序。 ZonedDateTime已经有您选择的时区中的时间,因此时间也是格式化时的时间:

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z");
    String databaseTime = now.format(formatter);

现在的结果是:

2020-09-27 16:22:23 +0200

进一步的编辑:由于该列仅是人类可读性的要求,因此请全力以赴,并使用Java的预定义本地化格式,例如:

    DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)
            .withLocale(Locale.GERMAN);
  1. 2020年9月19:06:03 MESZ

如果时间太长,请改用FormatStyle.MEDIUM

进一步编辑:为什么?问题是,27. September 2020 19:06:03 MESZ是否比2020-09-27 16:22:23 +0200更容易正确阅读和理解。您应该尽可能合理地使自己变得容易。但是,要包括偏移量+0200是有一点的,因为它是明确的,而不能保证像MESZ这样的时区缩写(许多时区缩写都是模棱两可)。

您的代码出了什么问题?

您可能正在将其时区设置为UTC(或当前比柏林时间晚两个小时的其他时区)的计算机上运行代码。在第二个片段中,您试图通过将格式化程序的时区设置为CEST(中欧夏令时)来弥补这一事实。您这样做的方式不是您想要的,它也不起作用。两者都与CEST不是时区这一事实有关。 CEST比UTC提前了两个小时,如果可以使用,那么在一年中的标准时间里,您也应该比UTC提前两个小时,而柏林仅比UTC提前1个小时,这就是错误的时间。由于CEST不是时区,因此TimeZone不会将其识别为时区。这和TimeZone类一样令人困惑:它不是默认的对象,而是默认给您的GMT,因此您无处可去。我真的建议避免使用该类。柏林的正确时区标识符是Europe/Berlin,这也是我在代码中使用的标识符。时区标识符采用 region / city 格式。

问题:java.time是否不需要Android API级别26?

java.time在旧的和更新的Android设备上均可正常运行。它只需要至少 Java 6

  • 在Java 8和更高版本以及更新的Android设备(API级别26以上)中,内置了现代API。
  • 在非Android Java 6和7中,获得ThreeTen Backport,这是现代类的backport(用于JSR 310的ThreeTen;请参见底部的链接)。
  • 在较旧的Android上,请使用废除旧书或Android版本的ThreeTen Backport。称为ThreeTenABP。在后一种情况下,请确保使用子包从org.threeten.bp导入日期和时间类。

链接

,

使用CEST代替import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; public class Main { public static void main(String[] args) { SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); sdf2.setTimeZone(TimeZone.getTimeZone("Europe/Berlin")); System.out.println(sdf2.format(new Date())); } } ,您将获得预期的结果。

2020-09-27 18:38:04 +0200

输出:

java.util

一条建议:

我建议您从过时且容易出错的SimpleDateFormat日期时间API和java.time切换到modern java.time.format日期时间API和相应的格式化API (软件包import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; public class Main { public static void main(String[] args) { ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Europe/Berlin")); // Default format System.out.println(zdt); // Some custom format System.out.println(zdt.format(DateTimeFormatter.ofPattern("EEEE dd uuuu hh:mm:ss a z"))); } } )。从 Trail: Date Time 了解有关现代日期时间API的更多信息。如果您的Android API级别仍不符合Java-8,请检查Java 8+ APIs available through desugaringHow to use ThreeTenABP in Android Project

使用现代的日期时间API:

2020-09-27T18:42:53.620168+02:00[Europe/Berlin]
Sunday 27 2020 06:42:53 pm CEST

输出:

import java.time.ZoneId;
import java.time.ZonedDateTime;

public class Main {
    public static void main(String[] args) {
        ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("CEST"));
        // ...
    }
}

现代API会提醒您,而旧API可能会进行故障转移:

Exception in thread "main" java.time.zone.ZoneRulesException: Unknown time-zone ID: CEST
    at java.base/java.time.zone.ZoneRulesProvider.getProvider(ZoneRulesProvider.java:279)
    at java.base/java.time.zone.ZoneRulesProvider.getRules(ZoneRulesProvider.java:234)
    at java.base/java.time.ZoneRegion.ofId(ZoneRegion.java:120)
    at java.base/java.time.ZoneId.of(ZoneId.java:408)
    at java.base/java.time.ZoneId.of(ZoneId.java:356)
    at Main.main(Main.java:6)

输出:

SimpleDateFormat

如您所见,在这种情况下您会得到一个例外,而import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; public class Main { public static void main(String[] args) { SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); sdf2.setTimeZone(TimeZone.getTimeZone("CEST")); System.out.println(sdf2.format(new Date())); } } 会给您带来不良的结果,如下所示:

2020-09-27 16:47:45 +0000

输出:

SimpleDateFormat

您可能想知道这种不良结果是指什么。 答案是:当GMT不了解时区时,它将故障转移(默认)到UTC(与CEST相同),即已忽略GMT,并在这种情况下应用了{{1}}(恕我直言,这不是一个好功能)。

,

嗯,一周前我遇到了同样的问题,我发现问题出在TimeZone设置中

如果您将日期作为字符串获取,并且需要将其格式化为另一种格式,请使用下面的代码

public String getCalendarDate(String inputDate){
        Date date = getDateFromSource(inputDate);
        SimpleDateFormat formatter = new SimpleDateFormat("EEEE,d MMMM yyyy",Locale.getDefault());
        formatter.setTimeZone(TimeZone.getDefault());
        return formatter.format(date);
    }

    Date getDateFromSource(String apiDate){
        Date newFormattedDate = null;
        SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'",Locale.ENGLISH);
        parser.setTimeZone(TimeZone.getTimeZone("UTC"));
        try {
             newFormattedDate = parser.parse(apiDate);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return newFormattedDate;
    }

getDateFromSource函数中,将日期格式更改为源格式,而在getCalendarDate函数中,将格式更改为所需的格式。

如果您已经有了Date对象,则可以忽略getDateFromSource函数并将其直接放在第二个对象中

对于使用Kotlin的用户,这是等效的代码

    fun getCalendarDate(apiDate: String): String{
        val date = getDateFromApi(apiDate)
        val formatter = SimpleDateFormat("EEEE,Locale.getDefault())
        formatter.timeZone = TimeZone.getDefault()
        return formatter.format(date)
    }

    private fun getDateFromApi(apiDate: String) :Date{
        val parser = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'",Locale.ENGLISH)
        parser.timeZone = TimeZone.getTimeZone("UTC")
        return parser.parse(apiDate)!!
    }

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...