[{"id":"b66f8b1e-cc14-4e89-9679-abd5687d283d","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"569bb90a-ea68-46c6-96f1-ab151c120714","tags":[{"product":null,"links":null,"id":"a2f84374-4a3f-4d22-96fd-765e9de495bf","name":"\u66F4\u65B0","color":"Coral","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"5eb52f08-2d1a-4362-9ffc-4871bdc10f3f","tags":[{"product":null,"links":null,"id":"a2f84374-4a3f-4d22-96fd-765e9de495bf","name":"\u66F4\u65B0","color":"Coral","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"341dd607-b97d-4d70-bde2-53acda6b6c95","tags":[{"product":null,"links":null,"id":"a2f84374-4a3f-4d22-96fd-765e9de495bf","name":"\u66F4\u65B0","color":"Coral","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"05349273-414f-4208-9ea2-c4fc8f4ea2cb","tags":[{"product":null,"links":null,"id":"a2f84374-4a3f-4d22-96fd-765e9de495bf","name":"\u66F4\u65B0","color":"Coral","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"86089f76-b778-4d52-821e-6f27de3df613","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"b81e4fd6-1fc5-43a0-a258-b6e16a5cbec6","tags":[{"product":null,"links":null,"id":"a2f84374-4a3f-4d22-96fd-765e9de495bf","name":"\u66F4\u65B0","color":"Coral","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"8aa8ce31-43e4-438e-951f-241608435260","tags":[{"product":null,"links":null,"id":"a2f84374-4a3f-4d22-96fd-765e9de495bf","name":"\u66F4\u65B0","color":"Coral","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"37343f41-6ec2-4c7e-b21d-2cc18d5ce1e0","tags":[{"product":null,"links":null,"id":"a2f84374-4a3f-4d22-96fd-765e9de495bf","name":"\u66F4\u65B0","color":"Coral","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"5915e52f-64f8-4146-b8bd-81bead6324a3","tags":[{"product":null,"links":null,"id":"a2f84374-4a3f-4d22-96fd-765e9de495bf","name":"\u66F4\u65B0","color":"Coral","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"836ba889-af9e-460d-a4cc-c24d922795f2","tags":[{"product":null,"links":null,"id":"a2f84374-4a3f-4d22-96fd-765e9de495bf","name":"\u66F4\u65B0","color":"Coral","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"4ae14b06-bb68-4394-a210-a46b8f028346","tags":[{"product":null,"links":null,"id":"a2f84374-4a3f-4d22-96fd-765e9de495bf","name":"\u66F4\u65B0","color":"Coral","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"d8f42066-e9dc-4411-bdcf-43b1a203370c","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"7ef86f16-b1a0-49f7-9592-612b9be02b25","tags":[{"product":null,"links":null,"id":"a2f84374-4a3f-4d22-96fd-765e9de495bf","name":"\u66F4\u65B0","color":"Coral","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"b29c7775-a9a4-451e-a1b5-01d19ed5ca5e","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"e56f3989-8f81-46af-90fa-a4813eeb976f","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"2526c963-f170-45a8-923e-91b0712a9810","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"24575cf0-501a-44f9-8426-c40f8f4b5552","tags":[{"product":null,"links":null,"id":"a2f84374-4a3f-4d22-96fd-765e9de495bf","name":"\u66F4\u65B0","color":"Coral","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"1a6f8d7f-acd6-42be-8c4f-f464c6218381","tags":[{"product":null,"links":null,"id":"a2f84374-4a3f-4d22-96fd-765e9de495bf","name":"\u66F4\u65B0","color":"Coral","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"82b176fd-5cab-498c-909e-8fa7d29c38d8","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"1708b3a9-4f37-44a8-8f0e-f9a2d2e5d940","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"c9ac246e-29fb-4bc4-8231-8439795bb590","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"94f2a5c3-2539-436a-af75-23fbbd1a3957","tags":[{"product":null,"links":null,"id":"a2f84374-4a3f-4d22-96fd-765e9de495bf","name":"\u66F4\u65B0","color":"Coral","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"d99594f4-2d40-4df4-9419-ba2ca6aa3f7f","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"fd66e72d-0f10-4f57-9807-6db26290ab2e","tags":[{"product":null,"links":null,"id":"a2f84374-4a3f-4d22-96fd-765e9de495bf","name":"\u66F4\u65B0","color":"Coral","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"8f344863-503d-4bc3-a594-3815e7d55f5c","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"9be82601-de9d-4c18-948a-23ab6f4dd431","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"6495f3c0-b463-47e0-b08a-ca949672211e","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"b4bede08-3f08-4839-ba4a-abc7ac195bde","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"4afcdfc0-3ff5-4f2b-a223-f8fc042a5bbe","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"422062c4-fa40-4771-a86f-008efe6d86e5","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"d87e8013-86a7-4840-8d25-6f62e14eb4ac","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"a6acedfd-4043-4c64-a5d1-aec3326df9e7","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"dbdc0b24-06c4-48b9-8d6c-7455119dc773","tags":[{"product":null,"links":null,"id":"9e117e35-984a-4c14-95ca-ef0ec7b9fb60","name":"\u65B0\u589E","color":"DarkGreen","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"a3856849-954a-4cfc-96a6-382e530d3638","tags":[{"product":null,"links":null,"id":"a2f84374-4a3f-4d22-96fd-765e9de495bf","name":"\u66F4\u65B0","color":"Coral","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]},{"id":"4a39306b-ffa5-433d-80a3-28e41f929b72","tags":[{"product":null,"links":null,"id":"a2f84374-4a3f-4d22-96fd-765e9de495bf","name":"\u66F4\u65B0","color":"Coral","productId":"098be112-50ec-44e4-b746-6bc8bf76af97"}]}]
        
(Showing Draft Content)

OADate 问题汇总

关于 OADate 常见的问题汇总

相信有用过日期并导出过 JSON 的小伙伴应该不难发现,当单元格的值是一个日期时,我们会将其存储为 OADate。

这是为了解决日期的序列化以及时区问题,所以我们用了这样一个特殊的方式保存日期。

Excel 也是对于日期格式的保存策略也是如此。在此做一个 OADate 格式造成的各种困扰做一个总结

问题 1:OADate 和 1900 年的关系

首先,关于 OADate。在我们取日期格式的值的时候发现拿到的值是个类似/OADate(42065)/这样的格式。

这个是 UTC 时间 1970/1/1 日到该日期的毫秒数。为了解决日期的序列化以及时区问题,所以我们用了这样一个特殊的方式保存日期。

SpreadJS 的处理方式是相当于把 OADate 当做一种“媒介”,在 SpreadJS 中我们会正常处理显示日期,存到数据库用的是 OADate,从数据库读取出来,然后拿到 SpreadJS 展示也还是正常的日期。

如果需要将 OADate 从数据库读取,做一些其他的数据处理,可以搜索相关语言对 OADate 的处理方法。

关于 1900 年这个时间问题,这是 Excel 和 js 日期计算的差异。在 Excel 中会把 1900 年当做闰年计算,而实际上 1900 年是平年,而闰年比平年多一天,所以 Excel 在 1900 年 2 月 29 前会多一天,也就是与 js 差一天。而在 1900 年 3 月 1 日后,js 的时间是与 Excel 一致了。

如果需要与 Excel 保持一致,则需要判断单元格的值是否在 1900/1/0 ~ 1900/2/29 这个区间,如果在,则就在 SpreadJS 的日期中减一。

顺带一说,OADate 是微软的时间格式,也就是后台 C#传到前端就是一个 OADate 的格式。

问题 2:为什么通过 getDataSource 方法获取到的日期数据是 OADate 格式的

问题代码:

function initSpreadJs(spread) {
  var spreadNS = GC.Spread.Sheets;

  var sheet = spread.getActiveSheet();

  var colInfos = [
    { name: "date", displayName: "日期", formatter: "yyyy-MM-dd" },
  ];

  sheet.bindColumns(colInfos);

  sheet.setDataSource([{ data: "1" }]);
}

原因:代码中 formatter 会改变数据类型,SpreadJS 会自动将之识别为日期格式,因此存储在 SpreadJS 中的值就是 OADate 类型了,且 autoFormatter 的机制触发也会发生该情况。如果希望避免这种问题,建议将 formatter 这一步操作放到后端处理,前端直接拿取后端返回的字符串即可

问题 3:如何将输入的日期传输到后台是字符串类型,从后台拿到的也是字符串类型

方式如下:

1. 通过代码:setValue 时给文本,比如 sheet.setValue(0,0,"2011-11-11");

2. 通过单元格格式代码:sheet.setFormatter(-1,3, "@");

如果是想让整个工作表都设置为单元格样式,那么可以通过 defaultStyle 来实现

var defaultStyle = sheet.getDefaultStyle();
defaultStyle.formatter = "@";
sheet.setDefaultStyle(defaultStyle);

3. 通过 UI 界面:先设置单元格格式为文本单元格,然后输入日期字符串值。

image

上述操作后,单元格记录的是文本值,你获取值拿到的也是字符串。且后续你可以继续设置日期单元格格式,来定义此日期字符串以需要的日期格式来显示

问题 4:日常日期格式在不同环境下转换为 OADate 格式

C#环境

DateTime date = DateTime.Now;
double oadate = date.ToOADate();

Python 环境

from datetime import datetime

date = datetime.now()
oadate = (date - datetime(1899, 12, 30)).total_seconds() / (24 * 60 * 60)

JS 环境

var date = new Date();
var oadate =
  (date.getTime() - Date.parse("1899-12-30")) / (24 * 60 * 60 * 1000);

Java 环境

import java.util.Date;

Date date = new Date();
double oadate = (date.getTime() - new Date(1899, 11, 30).getTime()) / (24 * 60 * 60 * 1000.0);

如果使用的是葡萄城的 GCExcel 产品,仅需一个属性设置便可以自动将日期字符串转换为 OADate 类型

// 例如日期为:"日期": "2011-11-11 11:11:22",
workbook.setAutoParse(true);// 在调用数据绑定前,设置该属性即可自动转换类型

问题 5:OADate 格式在不同环境下转换为日常日期格式

JS 环境

// OADate类型转日常使用日期格式
function fromOADate(date) {
  // 正则表达式
  var oaDateReg = new RegExp(
    "^/OADate\\(([-+]?(\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)\\)/\\s*"
  );

  if (typeof date === "string" && oaDateReg.test(date)) {
    var oadate = parseFloat(date.match(oaDateReg)[1]);
    var ms =
      (oadate * 86400000 * 1440 -
        25569 * 86400000 * 1440 +
        new Date((oadate - 25569) * 86400000).getTimezoneOffset() * 86400000) /
      1440;
    return new Date(ms);
  } else {
    return date;
  }
}

另外,还有一个取巧的办法就是通过 tag 进行一个转换,SpreadJS 的 tag 会将存入的值按照给定的内容进行类型转换

sheet.tag("/OADate(44542)/");
var date = sheet.tag();

如果想要结合数据绑定实现自定义的格式转换,可以这样做

var sheet = spread.getSheet(0);
sheet.autoGenerateColumns = false;
var colInfos = [
  { name: "id", displayName: "ID" },
  { name: "name", displayName: "Name", size: 100 },
  {
    name: "date",
    displayName: "Date",
    size: 80,
    formatter: "yyyy/mm/dd",
    value: function (item, value) {
      if (arguments.length == 2) {
        // 将oaDate类型转换成Date格式
        sheet.tag(value);
        let tempDate = sheet.tag();
        console.log(tempDate);
        if (tempDate instanceof Date) {
          item.date = tempDate.getTime();
        } else {
          item.date = value;
        }
      } else {
        // 时间戳
        if (item.date && item.date > 631123200000) {
          return new Date(item.date);
        } else {
          return item.date;
        }
      }
    },
  },
  { name: "date", displayName: "DateValue", size: 80 },
];
sheet.bindColumns(colInfos);
sheet.setDataSource([{ id: 1, date: 1817740800000 }, {}, {}]);

上述方法也可以通过自定义 convert 方法来实现,代码如下

// { name: "birthday", displayName: "birthday", size: 100, value: birthdayConverter },

function birthdayConverter(item, value) {
  if (arguments.length === 1) {
    return item["birthday"]();
  } else {
    item["birthday"](tryConvertOADateToDate(value));
  }
}
function tryConvertOADateToDate(t) {
  var _jsonOADateRegExp = new RegExp(
    "^/OADate\\(([-+]?(\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)\\)/\\s*$ "
  ); // match /OADate(9999.999)/
  if (typeof t === "string" && t.charAt(0) === "/") {
    //Date string is startwith '/'
    if (_jsonOADateRegExp.test(t)) {
      var x = t.match(_jsonOADateRegExp);
      t = fromOADate(parseFloat(x[1]));
    }
  }
  return t;
}

function fromOADate(oadate) {
  var offsetDay = oadate - 25569;
  var date = new Date(offsetDay * 86400000); // multiply 86400000 first then do divide. it will cause some float precision error if the order is not. // 2014/10/17 ben.yin here is a "+1" or "-1", is for javascript divide low precision, it will loss last digit precision.So here add 1, for loss, for result right. // add 1 when after 1987, sub 1 when before 1987
  var adjustValue = offsetDay >= 0 ? 1 : -1;
  return new Date(
    (oadate * 86400000 * 1440 +
      adjustValue -
      25569 * 86400000 * 1440 +
      date.getTimezoneOffset() * 86400000) /
      1440
  );
}

Java 环境

public class Test {
    public static void main(String[] args) throws ParseException {
        long d = 44542;
        double mantissa = d - (long) d;
        double hour = mantissa * 24;
        double min = (hour - (long) hour) * 60;
        double sec = (min - (long) min) * 60;

        SimpleDateFormat myFormat = new SimpleDateFormat("dd MM yyyy");
        Date baseDate = myFormat.parse("30 12 1899");
        Calendar c = Calendar.getInstance();
        c.setTime(baseDate);
        c.add(Calendar.DATE, (int) d);
        c.add(Calendar.HOUR, (int) hour);
        c.add(Calendar.MINUTE, (int) min);
        c.add(Calendar.SECOND, (int) sec);

        System.out.println(c.getTime());
    }
}

.NET 环境

System.DateTime.FromOADate(44542)

问题 6:数据绑定时,如果将日期设置为字符串格式,会导致 Spread 展示时无法自动 format

通过数据绑定中的 value 方法,来设置相互转换。value 本身有一个作用,就是在数据绑定时候,充当一个转换器,在数据源与真实显示的值之间做转换。

通过 value 方法,我们可以自由定义如何进行转换。

核心代码如下:

tableColumn1.value(function (item, value) {
  if (arguments.length > 1) {
    var oaTest = new RegExp(
      "^/OADate\\(([-+]?(\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)\\)/\\s*$"
    );
    if (oaTest.test(value)) {
      x = value.match(oaTest);
      var formatter = new GC.Spread.Formatter.GeneralFormatter("yyyy-mm-dd");
      item["date"] = formatter.format(format.parse(x[1]));
    } else {
      item["date"] = value;
    }
  } else {
    return new Date(item.date);
  }
});

这里需要一个判断 arguments.length > 1,通过判断代表双向绑定数据的来和去。

当 arguments.length > 1,里面的逻辑会在页面的数据同步给数据源时走的逻辑,反之则是数据同步给页面时走的逻辑。

完整 demo 请见论坛精华帖:数据绑定日期格式转换

问题 7:想要在 Spread 中嵌入一个日期事件选择器

官方有相关 demo 来实现需求:https://demo.grapecity.com.cn/spreadjs/SpreadJSTutorial/features/cells/drop-downs/date-time-picker/purejs

作者: GrapeCity China | 审核:AlexZ | 更新时间:2023.09.26