问题描述
我想将字符串与当前时间和日期一起存储到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);
- 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
导入日期和时间类。
链接
- Oracle tutorial: Date Time解释如何使用java.time。
- Java Specification Request (JSR) 310,其中首先描述了
java.time
。 - ThreeTen Backport project,即
java.time
向Java 6和7(JSR-310的ThreeTen)的反向端口。 - Java 8+ APIs available through desugaring
- ThreeTenABP,Android版本的ThreeTen Backport
- Question: How to use ThreeTenABP in Android Project,其中有非常详尽的解释。
- Wikipedia article: ISO 8601
使用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 desugaring和How 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)!!
}