我有以下形式的String
:
month
,其形式为01-12
day
是01-31
的形式
year
,例如: 2019
time
,我不知道它的形式,如果它可以包含毫秒或像是像HH:MM
所有这些都应该代表一个确切的“时间戳”,即与时区无关,但是例如如果我做了一个简化的例子:
day/month/year time
无论时区问题如何都应该被认为是正确的(我希望我能清楚地解释一下)。
我的问题是:从这些创建一些LocalTime
或类似对象的最佳方法是什么,以便它不会因为某些语言环境等设置而改变,我可以做任何我正确需要的字符串操作或获得一个纪元?
在我看来,你在这里问不可能或无意义。
让我们从评论中作为例子。一名乘客预订了抵达25/04/2019 13:10
伊斯坦布尔的航班。按照惯例,到达时间是在到达机场的当地时间。由于İstanbul位于UTC偏移+03:00,因此到达时间等于2019-04-25 10:10 UTC。
公认的良好做法是在数据库中以UTC格式存储日期和时间。如果您使用的是SQL数据库,通常应该使用其timestamp with time zone
数据类型(“with time zone”部分有点谎言,因为您无法使用您选择的时区存储时间戳;它始终是UTC,但我只是说这是好习惯,所以这很棒)。
假设您已将此作为用户的输入:25/04/2019 13:10
。将它转换为LocalDateTime
很容易(当你知道如何;下面的代码示例)。如果我们不知道时区(例如欧洲/伊斯坦布尔或亚洲/伊斯坦布尔)或UTC偏移(例如+03:00),则无法将其存储在UTC中。想象一下,乘客John Doe Jr.从未飞过,也不知道在抵达机场的当地时间到达时间。因此他13:10可以在他自己的时区13:10(美国/芝加哥,相当于18:10 UTC),13:10在出发时区(America / New_York,相当于17:10 UTC), 13:10 UTC,13:10在到达时区(等于10:10 UTC)或其他东西。
现在让我们说我们知道输入是在欧洲/伊斯坦布尔时区。然后转换为UTC很简单。我们将时间存储为2019-04-25 10:10 UTC。现在它不会因计算机或JVM的任何时区设置而改变。可以直接与数据库中的其他UTC时间进行比较,并且可以在执行此操作时安全地忽略时区。当您需要向用户显示时间(例如,在票证上打印)时,您将转换为伊斯坦布尔时间(13:10)(或用户想要的时区)。
除非您使用的是需要它们的API,否则请不要使用纪元时间。标准Java纪元定义为1970年1月1日00:00 UTC。请注意,它是以UTC定义的,因此它是一个定义明确的时间点。纪元时间是自纪元以来的有名秒数或毫秒数。我们的例子到达时间是从纪元以来的1 556 187 000秒,所以这是你的纪元时间。正如您所看到的,它并不意味着人类的可读性。您不希望破译日志文件或运行以这种方式表示时间的调试会话。您不希望对以这种方式表示时间的数据库进行任何查询。
远离字符串操作。您的日期和时间仅适用于日期和时间对象。收到字符串后,将其解析为适当的日期时间对象。只有当您需要将其呈现给用户或将其作为字符串传输到另一个系统时,才将其格式化为字符串以用于此目的。
Java为我们提供以下日期和时间类型:
LocalDateTime
是没有UTC偏移或时区的日期和时间,例如2019-04-25T13:10
。所以它没有定义一个时间点。Instant
是没有UTC偏移或时区的时间点。因此它没有定义日期和时间。它以UTC(例如,2019-04-25T10:10Z
)打印,并在内部表示为自纪元以来的秒和纳秒的计数。OffsetDateTime
是UTC偏移的日期和时间,例如2018-04-25T13:10+03:00
。因此,这确定了一个时间点,并确定了日期和时间。ZonedDateTime
是带时区的日期和时间,例如2018-04-25T13:10+03:00[Europe/Istanbul]
。因此,这也确定了一个时间点,并确定了日期和时间。 DateTimeFormatter userInputFormatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm");
String userInput = "25/04/2019 13:10";
ZoneId arrivalTimeZone = ZoneId.of("Europe/Istanbul");
// Parse into a LocalDateTime
LocalDateTime arrivalTimeLocal = LocalDateTime.parse(userInput, userInputFormatter);
System.out.println("Arrival time: " + arrivalTimeLocal);
到货时间:2019-04-25T13:10
// Convert to Instant in order to have a well-defined point in time
Instant arrivalTime = arrivalTimeLocal.atZone(arrivalTimeZone).toInstant();
System.out.println("Arrival point in time (printed in UTC): " + arrivalTime);
到达时间点(以UTC打印):2019-04-25T10:10:00Z
// Convert to UTC for storing in SQL database
OffsetDateTime arrivalTimeUtc = arrivalTime.atOffset(ZoneOffset.UTC);
System.out.println("Arrival time in UTC: " + arrivalTimeUtc);
到达时间UTC:2019-04-25T10:10Z
编辑:正如我所说,在SQL数据库中,您通常希望以数据类型timestamp with time zone
的列保存UTC中的日期和时间。详细信息取决于您的数据库和JDBC驱动程序。我相信在MySQL(以及可能的其他DBMS)中,类型只是timestamp
。这是一个典型的例子:
// Save into a database column of datatype timestamp with time zone
PreparedStatement insert = yourDatabaseConnection.prepareStatement(
"insert into your_table (your_timestamp_with_time_zone_column) values (?);");
insert.setObject(1, arrivalTimeUtc);
int rowsInserted = insert.executeUpdate();
如果SQLite没有时区或日期时间数据类型,则更喜欢将ISO 8601格式存储在字符列中,因为这比数字列中的纪元时间更具可读性。 Instant.toString()
生成你需要的字符串:
PreparedStatement insert = yourDatabaseConnection.prepareStatement(
"insert into your_table (your_varchar_column) values (?);");
insert.setString(1, arrivalTime.toString());
int rowsInserted = insert.executeUpdate();
Instant.parse
将在检索后解析相同的字符串。
// Convert to arrival time zone, e.g., for printing on ticket
String arrivalTimeForUser = arrivalTime.atZone(arrivalTimeZone)
.format(userInputFormatter);
System.out.println("Formatted arrival time in local time: " + arrivalTimeForUser);
格式当地时间:25/04/2019 13:10