C#与Java中的DateTime操作

DateTime Manipulation in C# vs Java

我是Java的新手。 我有时间从网页上获取,这是"hh:mm"格式(不是24小时)。 这对我来说是一个字符串。 然后我想将这个字符串与今天的日期结合起来,以便创建一个我可以使用的Java Date

在C#中:

1
2
3
string s ="5:45 PM";
DateTime d;
DateTime.TryParse(s, out d);

在Java中我尝试过:

1
2
3
4
5
6
7
8
9
10
11
String s ="5:45 PM";
Date d = new Date(); // Which instantiates with the current date/time.

String[] arr = s.split("");
boolean isPm = arr[1].compareToIgnoreCase("PM") == 0;

arr = arr[0].split(":");
int hours = Integer.parseInt(arr[0]);
d.setHours(isPm ? hours + 12 : hours);
d.setMinutes(Integer.parseInt(arr[1]));
d.setSeconds(0);

有没有更好的方法来实现我想要的?


Is there a better way to achieve what I want?

绝对 - 实际上在.NET和Java中都是如此。在.NET中,我(以偏向的方式)建议使用Noda Time,这样您就可以将一天中的某个时间表示为LocalTime,从而精确地解析您期望的模式。

在Java 8中,您可以使用java.time.LocalTime执行相同的操作:

1
2
3
4
5
6
7
8
9
10
11
import java.time.*;
import java.time.format.*;

public class Test {
    public static void main(String[] args) {
        String text ="5:45 PM";
        DateTimeFormatter format = DateTimeFormatter.ofPattern("h:mm a");
        LocalTime time = LocalTime.parse(text, format);
        System.out.println(time);
    }
}

一旦你解析了你已经进入适当类型的文本,就可以将它与其他类型结合起来。例如,要使用今天的日期和指定的时间在系统时区中获取ZonedDateTime,您可以使用:

1
ZonedDateTime zoned = ZonedDateTime.now().with(time);

默认情况下使用系统时区和时钟,使其难以测试 - 我建议传入Clock以获得可测试性。

(Noda Time也提供同样的东西,但略有不同。如果您需要详细信息,请告诉我。)

我强烈建议不要使用java.util.Date,它只是代表一个时刻,并且有一个糟糕的API。

这里的关键点是:

  • 使用明确指定的格式解析文本
  • 将文本解析为表示其传达的信息的类型:一天中的某个时间
  • 将该值与另一个值相结合,该值也应该仔细指定(就时钟和时区而言)

所有这些都将导致清晰,可靠,可测试的代码。 (并且现有的.NET代码不符合任何这些要点,IMO。)


要解析时间,你可以按@Jon Skeet的答案解释:

1
2
3
String input ="5:45 PM";
DateTimeFormatter parser = DateTimeFormatter.ofPattern("h:mm a", Locale.ENGLISH);
LocalTime time = LocalTime.parse(input, parser);

请注意,我还使用了java.util.Locale,因为如果您不指定它,它将使用系统的默认语言环境 - 并且某些语言环境可以对AM / PM字段使用不同的符号。使用显式语言环境可避免出现这种情况(即使在运行时也可以更改默认语言环境,因此最好使用显式语言环境)。

要结合今天的日期,您需要java.time.LocalDate(获取日期)并与LocalTime组合,以获得LocalDateTime

1
2
// combine with today's date
LocalDateTime combined = LocalDate.now().atTime(time);

然后你可以使用另一个格式化器格式化LocalDateTime

1
2
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
System.out.println(combined.format(fmt));

输出是:

16/08/2017 17:45

如果要将LocalDateTime转换为java.util.Date,则必须注意一些细节。

A java.util.Date表示自1970-01-01T00:00Z(又名Unix Epoch)以来的毫秒数。这是一个瞬间(一个特定的时间点)。有关详细信息,请查看此文章。

因此,相同的Date对象可以表示不同的日期或时间,具体取决于您所处的位置:现在想想,此时此世界上的每个人都处于同一时刻(自1970-01-01T00:00Z以来的相同毫秒数) ),但世界各地的当地日期和时间不同。

LocalDateTime表示"本地"的概念:它是日期(日,月和年)和时间(小时,分钟,秒和纳秒),但与特定时区无关。

相同的LocalDateTime对象可以表示不同时区的不同时刻。因此,要将其转换为Date,您必须定义您想要的时区。

一种选择是使用系统的默认时区:

1
2
3
4
// convert to system's default timezone
ZonedDateTime atDefaultTimezone = combined.atZone(ZoneId.systemDefault());
// convert to java.util.Date
Date date = Date.from(atDefaultTimezone.toInstant());

但是默认值可能因系统/环境而异,也可以在运行时更改。要不依赖于它并对其进行更多控制,您可以使用显式区域:

1
2
3
4
// convert to a specific timezone
ZonedDateTime zdt = combined.atZone(ZoneId.of("Europe/London"));
// convert to java.util.Date
Date date = Date.from(zdt.toInstant());

请注意,我使用Europe/London。 API使用IANA时区名称(始终采用Region/City格式,如America/Sao_PauloEurope/Berlin)。
避免使用3个字母的缩写(如CSTPST),因为它们不明确且不标准。

您可以通过调用ZoneId.getAvailableZoneIds()获取可用时区列表(并选择最适合您系统的时区)。

此外还有夏令时的极端情况(当LocalDateTime可以存在两次或由于重叠和间隙而不能存在时)。在这种情况下,使用ZonedDateTime的Jon的解决方案避免了这个问题)。