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

时间:2017-10-21 05:19:30

标签: java date datetime datetimeoffset

我有要求

  • 以GMT时区保存并检索日期(日期应转换为字符串)。因此,如果用户保存日期 10/10/2017 23:05 ,则会保存为 10/11/2017 4:05 (如果保存在CST中,则提前5小时)例如,在DB中的时间。
  • 在检索和向UI展示日期时,它应显示为 10/10/2017 23:05 ,供CST用户使用。
  • 此外,需要验证一项功能,以了解日期是否需要以美国/非美国日期格式显示(dd / MM / YYYY与mm / DD / YYYY)。

为实现这一目标,我已在下面的代码段中编码,但是没有产生所需的结果。它存储的值 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;
    }
}

2 个答案:

答案 0 :(得分:5)

java.time

你正在使用可怕的旧日期时间类,这些类很麻烦,容易混淆,而且设计很差。它们现在已经遗留下来,取而代之的是java.time类。避免使用DateCalendarSimpleDateFormat等。

使用实时区域

CST你的意思是中央标准时间还是中国标准时间?

continent/region的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用诸如ESTIST之类的3-4字母缩写,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

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

与用户

确认时区

如果时区对您的工作至关重要,您必须确认其输入所针对的区域。有一些方法可以猜测区域或检测默认值,但重要的是,使区域成为数据输入的一部分以及日期和时间。您可以从中选择present a list,或让他们输入字符串名称。

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

解析并组装

  

以GMT时区保存并检索日期(日期应转换为字符串)。因此,如果用户保存日期10/10/2017 23:05,那么将保存为10/11/2017 4:05(例如,如果在CST时间保存为5小时,则保存在DB中)。

使用LocalDate将用户输入解析为LocalTimeDateTimeFormatter

在实际工作中,您可以添加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 ) ;
  

在检索和向UI显示日期时,CST用户应显示为10/10/2017 23:05。

调整到用户期望/期望的任何时区。

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

生成文本表示

  

此外,需要验证一个功能,以了解日期是否需要以美国/非美国日期格式显示(dd / MM / YYYY与mm / DD / YYYY)。

同样,在生成表示该时刻的文本时,会自动使用用户期望/期望的Locale进行本地化。

要进行本地化,请指定:

  • 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框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendar和& SimpleDateFormat

现在位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuartermore

答案 1 :(得分:2)

java.time

我全心全意地同意Basil Bourque’s thorough and very knowledgeable answer。您的格式是旧的,与使用旧的和过时的日期和时间类无关。使用现代代码会导致代码更自然地出现,并且更容易避免像您所问的那样的问题。还要使用格式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 格式的使用。您可以使用userTimeZone代替ZoneId.systemDefault()变量,但正如我所说,在同一JVM中运行的其他程序可能会在您的脚下改变。

如果您希望对用户界面进行现代化改造,可以使用DateTimeFormatter.ofLocalizedDateTime()代替硬编码显示格式,如Basil Bourque所述。

您的代码出了什么问题?

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

小点:

  • 您不需要在正在执行此操作的两个位置中的任何位置转换df.parse()的返回值,因为已经声明该方法返回Date
  • 您无需在toString()上致电dateValue,因为它已被宣布为String,因此呼叫将再次返回相同的String