在 GMT 时区中保存和检索日期并转换为字符串

Saving and retrieving Date in GMT timezone and converting to String

我有一个要求

为此,我编写了以下代码片段,但未产生所需的结果。它正在存储值 10/11/2017 4:05,但是,当向我们展示时,即获取值/刷新页面,它又增加了 5 个小时。删除了异常和其他不必要的代码以使其变得简单:

public class DatetoString implements Serializable
{
    private final DateFormat dateFormatter = createDateFormatter();

    // Sets Date to model
    public void setTypedValue(final Object val)
    {
        final String dateValue;
        String dateTimeFormat = BooleanUtils.isFalse(getUSDateFormatConfig()) ? "dd/MM/yyyy HH:mm" : "MM/dd/yyyy HH:mm";
        DateFormat df = new SimpleDateFormat(dateTimeFormat);
        df.setTimeZone(TimeZone.getTimeZone("GMT"));

        Date singleDate = (Date) df.parse(val.toString());
        dateValue = dateFormatter.format(singleDate);
        model.setValue(dateValue.toString());
        // Other code..
    }

    // Retrieves date from model
    public Object getTypedValue()
    {
        final Object result;
        String dateValue = model.iterator().next().getValue();

        String dateTimeFormat = BooleanUtils.isFalse(getUSDateFormatConfig()) ? "dd/MM/yyyy HH:mm" : "MM/dd/yyyy HH:mm";
        DateFormat df = new SimpleDateFormat(dateTimeFormat);
        df.setTimeZone(TimeZone.getTimeZone("GMT"));

        Date singleDate = (Date) df.parse(dateValue);
        result = dateFormatter.format(singleDate);
        return result;
    }

    private DateFormat createDateFormatter()
    {
        final DateFormat result = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
        result.setTimeZone(TimeZone.getTimeZone("GMT"));
        return result;
    }
}

java.time

您正在使用可怕的旧日期时间 类,它们很麻烦、令人困惑且设计不佳。它们现在是遗留的,被 java.time 类 取代。避免 DateCalendarSimpleDateFormat 等。

使用实时时区

CST 你指的是中部标准时间还是中国标准时间?

指定 proper time zone name in the format of continent/region, such as America/Montreal, Africa/CasablancaPacific/Auckland。切勿使用 ESTIST 等 3-4 字母缩写,因为它们 不是 真正的时区,未标准化,甚至不是唯一的(!)。

ZoneId z = ZoneId.of( "America/Chicago" );

与用户确认时区

如果时区对您的工作至关重要,您必须确认他们输入的是哪个时区。有多种方法可以猜测区域或检测默认值,但重要的是,将区域与日期和时间一起作为数据输入的一部分。你可以present a list他们从中选择,或者让他们输入字符串名称。

同上Locale(下面讨论)。你可以猜,但如果关键,请问。

解析和assemble

Save and retrieve the date in GMT timezone (date should be converted to String). So, if user saves date 10/10/2017 23:05, that will be saved as 10/11/2017 4:05 (5 hours ahead if saved in CST time for e.g.) in DB.

将用户输入解析为 LocalDate and LocalTime using a DateTimeFormatter

在实际工作中,您将添加 try-catch 以捕获由错误的用户输入引发的 DateTimeParseException

DateTimeFormatter fDate = DateTimeFormatter.ofPattern( "MM/dd/uuuu" ) ;
LocalDate ld = LocalDate.parse( inputDate , f ) ;

DateTimeFormatter fTime = DateTimeFormatter.ISO_LOCAL_TIME ; 
LocalTime lt = LocalTime.parse( inputTime , f ) ;

合并,并指定时区得到一个ZonedDateTime对象

ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;

通过提取根据定义始终采用 UTC 的 Instant 来调整为 UTC。同一时刻,时间轴上的同一点,但通过不同挂钟的镜头观看。

Instant instant = zdt.toInstant() ;

数据库

持久化到您的数据库,在 TIMESTAMP WITH TIME ZONE 类型的列中。另一种类型 WITHOUT 忽略任何时区或与 UTC 的偏移量信息,并且绝对 不是 您想要跟踪的实际时刻。

myPreparedStatement.setObject( … , instant ) ;

从数据库中检索。

Instant instant = myResultSet.getObject( … , Instant.class ) ;

While retrieving and presenting the date to UI, it should show as 10/10/2017 23:05 for CST users.

调整到用户所在的任何时区 expects/desires。

ZoneId z = ZoneId.of( "Asia/Kolkata" ) ;  // Or "America/Chicago" or "America/Winnipeg" etc.
ZonedDateTime zdt = instant.atZone( z ) ;

生成文本表示

Also, need to verify a function to know if the date needs to be shown in US/Non-US date format (dd/MM/YYYY vs mm/DD/YYYY).

同样,在生成表示该时刻的文本时,自动本地化 Locale 用户 expects/desires。

要本地化,请指定:

  • FormatStyle 确定字符串的长度或缩写。
  • Locale 以确定 (a) 用于翻译日期名称、月份名称等的人类语言,以及 (b) 决定缩写、大写、标点符号、分隔符等问题的文化规范,诸如此类

示例:

Locale l = Locale.FRANCE ;  // Or Locale.US etc.
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.LONG ).withLocale( l ) ;
String output = zdt.format( f ) ;

请注意,Locale 和时区是正交的、无关的和独立的。您可以在摩洛哥聘请一名讲法语的店员跟踪印度客户的送货情况。所以时刻以UTC存储在加拿大服务器上的数据库运行中,在UTC数据库和其他组件之间交换,调整到印度时区以解决客户收货的角度,并本地化为法语以供阅读来自摩洛哥的用户。


关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

Joda-Time project, now in maintenance mode, advises migration to the java.time 类.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

java.time类在哪里获取?

ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

java.time

我完全同意。您的格式很旧,与使用旧的和过时的日期和时间无关类。使用现代的代码会产生更自然的代码,并且更容易避免像您所问的那样的问题。还要使用 region/city 格式的时区名称,并注意您的 JVM 的默认时区设置可能会在运行时被同一 JVM 中的其他程序 运行 更改。

编辑:我不想从一开始就提供代码来破坏它,但现在你已经解决了你的问题,对于任何阅读的人来说,这里是:

private static final DateTimeFormatter storeFormatter 
        = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss");
private static final DateTimeFormatter usDisplayFormatter 
        = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm");
private static final DateTimeFormatter internationalDisplayFormatter 
        = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");

private ZoneId userTimeZone = ZoneId.of("America/Rosario");

/** Sets Date to model */
public void setTypedValue(final Object val)
{
    DateTimeFormatter parseFormatter = isUSDateFormatConfig()
            ? usDisplayFormatter : internationalDisplayFormatter;
    final String dateValue = LocalDateTime.parse(val.toString(), parseFormatter)
            .atZone(userTimeZone)
            .withZoneSameInstant(ZoneOffset.UTC)
            .format(storeFormatter);
    model.setValue(dateValue);
    // Other code..
}

/** Retrieves date from model */
public Object getTypedValue()
{
    String dateValue = model.iterator().next().getValue();
    DateTimeFormatter displayFormatter = isUSDateFormatConfig()
            ? usDisplayFormatter : internationalDisplayFormatter;

    final Object result = LocalDateTime.parse(dateValue, storeFormatter)
            .atOffset(ZoneOffset.UTC)
            .atZoneSameInstant(userTimeZone)
            .format(displayFormatter);
    return result;
}

我调用了 setTypedValue("10/29/2017 21:30"),日期时间存储为 10/30/2017 00:30:00。我能够将其检索为美国的 10/29/2017 21:30 和美国以外的 29/10/2017 21:30

现在我将用户的时区硬编码为 America/Rosario 只是为了演示 region/city 格式的使用。您当然可以使用 ZoneId.systemDefault() 而不是 userTimeZone 变量,但正如我所说,这可能会被同一 JVM 中的其他程序 运行 在您的脚下更改。

如果您想使您的用户界面现代化,您可以使用 DateTimeFormatter.ofLocalizedDateTime() 而不是硬编码的显示格式,正如 Basil Bourque 也提到的那样。

你的代码有什么问题?

在我看来,在问题的代码中,您在 setTypedValuegetTypedValue 中进行了类似的转换。你不应该做相反的转换吗?我想在 getTypedValue 中你应该使用 dateFormatter (最终实例变量)从 GMT 解析,然后使用本地时区(不是 GMT)进行格式化的格式化程序。

要点:

  • 您不需要在任何两个地方从 df.parse() 转换 return 值,因为已经声明该方法 return 是一个Date.
  • 您不需要在 dateValue 上调用 toString(),因为它已经被声明为 String,因此调用将 return 与 [=27] 相同=] 再次。