【问题标题】:GSON default date serializer is locale-specificGSON 默认日期序列化程序是特定于语言环境的
【发布时间】:2019-11-02 07:16:42
【问题描述】:

在我的 java 应用程序中,我使用了以 JSON 格式返回数据的 REST API,并注意到这个特定的 API 以一种特殊的方式格式化了它的日期: “2019 年 11 月 1 日”,但问题是服务器上的实际日期是“2019-11-02”。这意味着我将日期最小化为上一个日期。我的服务器在另一个国家。下面是完整的 json我是格式化后得到的。

jsonAccts [{"id":8,"userId":2,"departmentId":45,"effectiveFrom":"Jun 9, 2019","endsOn":"Nov 1, 2019","createdBy":2,"createdOn":"2019-11-02 05:34:11"}]

在所有日期字段上都有这个问题。我有什么解决方案。我需要在 REST API 请求的数据库上获取相同的日期。 下面是我用于 gson 格式化的代码。

    Gson gson;
    GsonBuilder builder;
    SimpleDateFormat dtf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
    SimpleDateFormat dtfDate=new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
    String jsonAccts = null;
    try{
        builder = new GsonBuilder();
        builder.registerTypeAdapter(Timestamp.class, new JsonSerializer<Timestamp>() {
            @Override
            public JsonElement serialize(Timestamp src, Type typeOfSrc, JsonSerializationContext context) {
                dtf.setTimeZone(TimeZone.getTimeZone("UTC"));
                String jsDate = dtf.format(src);
                return new JsonPrimitive(jsDate);
            }
        });
        gson = builder.create();
        List<ClassTeacher> allActPgmMap = new ArrayList<ClassTeacher>();
        allActPgmMap = springDao.getClassTeacherList(Integer.parseInt(deptId));
        Type listType = new TypeToken<List<ClassTeacher>>() {}.getType();
        jsonAccts = gson.toJson(allActPgmMap, listType);
    }catch(Exception e){
        e.printStackTrace();
    }
    return jsonAccts;

下面是 ClassTeacher 类。

import javax.persistence.*;
    import java.io.Serializable;
    import java.sql.Timestamp;
    import java.util.Date;

    @Entity
    @Table(name = "class_teacher")
    public class ClassTeacher implements Serializable,Comparable {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id")
        private long id;

        @Column(name = "user_id")
        private long userId;

        @Column(name = "department_id")
        private long departmentId;

        @Column(name = "effective_from")
        @Temporal(TemporalType.DATE)     
        private Date effectiveFrom;

        @Column(name = "ends_on")
        @Temporal(TemporalType.DATE)     
        private Date endsOn;

        @Column(name="created_by")
        private long createdBy;

        @Column(name="created_on")
        private Timestamp createdOn;

        public ClassTeacher() {

        }

        public ClassTeacher(long id, long departmentId, long userId, Date effectiveFrom, Date endsOn, long createdBy, Timestamp createdOn) {
            this.id = id;
            this.departmentId = departmentId;
            this.userId = userId;
            this.effectiveFrom = effectiveFrom;
            this.endsOn = endsOn;
            this.createdBy = createdBy;
            this.createdOn = createdOn;
        }

        public long getId() {
            return id;
        }

        public void setId(long id) {
            this.id = id;
        }

        public long getUserId() {
            return userId;
        }

        public void setUserId(long userId) {
            this.userId = userId;
        }

        public long getDepartmentId() {
            return departmentId;
        }

        public void setDepartmentId(long departmentId) {
            this.departmentId = departmentId;
        }

        public Date getEffectiveFrom() {
            return effectiveFrom;
        }

        public void setEffectiveFrom(Date effectiveFrom) {
            this.effectiveFrom = effectiveFrom;
        }

        public Date getEndsOn() {
            return endsOn;
        }

        public void setEndsOn(Date endsOn) {
            this.endsOn = endsOn;
        }

        public long getCreatedBy() {
            return createdBy;
        }

        public void setCreatedBy(long createdBy) {
            this.createdBy = createdBy;
        }

        public Timestamp getCreatedOn() {
            return createdOn;
        }

        public void setCreatedOn(Timestamp createdOn) {
            this.createdOn = createdOn;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            ClassTeacher that = (ClassTeacher) o;

            if (id != that.id) return false;
            if (createdBy != that.createdBy) return false;
            if (userId != null ? !userId.equals(that.userId) : that.userId != null) return false;
            if (departmentId != null ? !departmentId.equals(that.departmentId) : that.departmentId != null) return false;
            if (effectiveFrom != null ? !effectiveFrom.equals(that.effectiveFrom) : that.effectiveFrom != null)
                return false;
            if (endsOn != null ? !endsOn.equals(that.endsOn) : that.endsOn != null) return false;
            if (createdOn != null ? !createdOn.equals(that.createdOn) : that.createdOn != null) return false;
        }

        @Override
        public int hashCode() {
            int result = (int) (id ^ (id >>> 32));
            result = 31 * result + (userId != null ? userId.hashCode() : 0);
            result = 31 * result + (departmentId != null ? departmentId.hashCode() : 0);
            result = 31 * result + (effectiveFrom != null ? effectiveFrom.hashCode() : 0);
            result = 31 * result + (endsOn != null ? endsOn.hashCode() : 0);
            result = 31 * result + (int) (createdBy ^ (createdBy >>> 32));
            result = 31 * result + (createdOn != null ? createdOn.hashCode() : 0);
            return result;
        }

        @Override
        public int compareTo(Object comparestu) {
            return this.getEffectiveFrom().compareTo(((ClassTeacher)comparestu).getEffectiveFrom());
        }
    }

【问题讨论】:

  • 您注册了使用 UTC 时区的时间戳类型适配器。您是否尝试将此时区设置为与数据库时区匹配?

标签: java json date gson utc


【解决方案1】:

问题是服务器将日期返回为“2019 年 11 月 1 日”。在这种格式中,您没有时间和时区,因此您将无法正确格式化日期。如果您无法更改服务器端,我看不到任何解决方案,否则解决方案很少:

1) 当您拨打电话时,将语言环境发送到服务器,它会返回正确的日期。

2) 更改服务器的日期格式,以毫秒或ISO 8061 格式返回时间 - 然后当您解析日期时,您可以设置您的语言环境。

【讨论】:

  • 我使用@Temporal(TemporalType.DATE) 作为日期值,这就是为什么以该格式返回日期的原因,如果我将其删除,则 endsOn >= :checkDate 如果两个日期相同,则条件将不起作用。
  • 以日期字段表示出生日期为例,则不需要将时间与日期一起使用。
  • 同样,您必须注意时间,至少在保存日期时是这样。假设服务器时间比客户端时间早 2 小时。如果您在 11 月 2 日 00:20(客户端时间)创建日期,则服务器时间将为 11 月 1 日 22:20,然后您只保存日期(使用时间注释)。如您所见,日期为 11 月 1 日,时间已丢失,因此您无法将日期返回到 11 月 2 日。一种解决方案是客户端(创建日期时)和服务器(读取日期时) ) 使用相同的语言环境。
  • 我在从客户端接收日期值时使用 UTC 时区使用 gson 反序列化,因此当我在服务器中保存日期时没有此类问题。如果客户端将 nov 1 作为日期传递,则服务器将其保存为 nov 1.
  • 您在 Locale.England 中使用 UTC,即 UTC+1。但是客户端语言环境呢?如果客户端语言环境在美国的某个地方,例如 UTC-6,该怎么办。你知道客户端的语言环境吗?(或者它是默认语言环境,这意味着它取决于客户端的位置)
【解决方案2】:

你的问题比这更深。格式化任何类型的日期或时间对象而不包括时区偏移总是模棱两可的。这个时间点:

1.1.1970 00:00

可以解释为(在 UTC 中)

1.1.1970 14:00 (in kiribati, UTC+14)

31.12.1969 12:00 (in honolulu, UTC -12)

为了完整准确,您必须包括时区偏移:

1970-01-01T00:00:00+00:00

相应的标准是 ISO 8601 (https://en.wikipedia.org/wiki/ISO_8601),或者您将参考作为接口合同的一部分进行交流(即在文档中,按照“所有日期均以 UTC 表示”的内容说一些话),即,坦率地说,相当麻烦。

在大多数情况下,我现在在 API 中使用纪元毫秒或纪元秒作为日期。这使得您所指的确切时间点更加清晰。

在某些情况下(例如,在计算商店的营业时间时),您还需要单独存储时区信息,最好是作为对地理位置的特定参考(以“America/New_York”的形式而不是“EST”),因为夏令时可能因地区而异,即使这些地区有时在 EST 上(例如,美国的大多数地方都有 DST,而加拿大的一些地方全年都在 EST 上)- 进一步阅读 @987654322 @

java Date 对象在格式化之前不知道时区,此时它将(默认情况下)使用系统时区来创建默认格式化程序。

解析日期也是如此:解析后,所有时区信息都会丢失。

TLDR:

简而言之:依赖任何类型的时区协议可能不是一个好主意,您可能应该考虑将其包含在所有日期字段中。

但这并不是专门针对您的问题,因为您要问的是如何设置 GSON 以在全局范围内使用序列化器和反序列化器。像往常一样,这取决于谁在进行反序列化。

这篇文章可能会有所帮助:

https://futurestud.io/tutorials/gson-advanced-custom-serialization-part-1

但是,我发现“时代毫秒”方法更容易:只需将“long”类型的字段声明为日期,并在需要时转换为日期。这也适用于带有

的数据库查询

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-03-09
    • 2014-01-18
    • 1970-01-01
    • 2014-03-31
    • 1970-01-01
    • 2015-02-06
    • 2018-09-10
    • 1970-01-01
    相关资源
    最近更新 更多