antd 1.x datepicker 时区问题

Antd(1.x stable) - DatePicker

依赖关系

  • 底层引用了 rc-calender(~5.6.2)

  • 封装过程中使用了wrapPicker方法和createPicker方法

    const DatePicker = wrapPicker(createPicker(RcCalendar));
    const MonthPicker = wrapPicker(createPicker(MonthCalendar), 'yyyy-MM');
  • DatePicker的Date处理函数,依赖gregorian-calendar(4.1.0)

  • gregorian-calendar内置了en_USzh_CN两个locale配置文件,内置的时间处理对象GregorianCalendar默认使用en_US.

    antd全局以及各组件配置了不同的locale,分别处理不同的场景,但是DatePicker最底层是直接使用了gregorian-calendar中的配置

    /*
     * en-us locale
     * @ignore
     * @author yiminghe@gmail.com
     */
    module.exports = {
      // in minutes
      timezoneOffset: -8 * 60,
      firstDayOfWeek: 0,
      minimalDaysInFirstWeek: 1,
    };
    /*
     * zh-cn locale
     * @ignore
     * @author yiminghe@gmail.com
     */
    module.exports = {
      // in minutes
      timezoneOffset: 8 * 60,
      firstDayOfWeek: 1,
      minimalDaysInFirstWeek: 1,
    };

代码逻辑分析

  • wrapPicker方法默认传递一些参数给createPicker默认传入zh_CN。同时会从antd的全局locale方法中寻找DatePicker的locale配置

    getLocale() {
      const props = this.props;
      let locale = defaultLocale;
      const context = this.context;
      if (context.antLocale && context.antLocale.DatePicker) {
        locale = context.antLocale.DatePicker;
      }
      // 统一合并为完整的 Locale
      const result = { ...locale, ...props.locale };
      result.lang = { ...locale.lang, ...props.locale.lang };
      return result;
    }
  • createPicker将props传入的locale,传递给GregorianCalendar对象生成基本的locale信息

    const props = this.props;
    const locale = props.locale;
    // 以下两行代码
    // 给没有初始值的日期选择框提供本地化信息
    // 否则会以周日开始排
    let defaultCalendarValue = new GregorianCalendar(locale);
    defaultCalendarValue.setTime(Date.now());
  • rc-calender根据locale信息进行组件渲染

    <TheCalendar
      ...
      formatter={props.getFormatter()}
      locale={locale.lang}
      ... />

核心问题

  • 1.x 的antd datepick在使用时,不根据用户的new Date().timezoneOffset设置日期,而是根据默认的local配置进行设置。分析过程见上述“代码逻辑分析”

  • 外层的antd的LocaleProvider包装组件会修改全局的locale,因此双语切换时,会存在用户的datepicker组件时区被改写的问题

解决方案

  • wrapPicker方法中会从全局的antLocale.DatePicker locale配置,将此处的timezoneOffset覆写即可

    // wrapPicker.jsx中寻找DatePicker local而配置的判断逻辑
    if (context.antLocale && context.antLocale.DatePicker) {
      locale = context.antLocale.DatePicker;
    }

深入LocaleProvider的配置

LocaleProvider的使用

import enUS from 'antd/lib/locale-provider/en_US';

...

return <LocaleProvider locale={enUS}><App /></LocaleProvider>;

locale的配置,以en_US为例

// antd/lib/locale-provider/en_US
import DatePicker from '../date-picker/locale/en_US';
import TimePicker from '../time-picker/locale/en_US';

export default {
  ...
  DatePicker,
  TimePicker,
  ...
}; 


// date-picker/locale/en_US
import GregorianCalendarLocale from 'gregorian-calendar/lib/locale/en_US';
import CalendarLocale from 'rc-calendar/lib/locale/en_US';
import TimePickerLocale from '../../time-picker/locale/en_US';

// 统一合并为完整的 Locale
const locale = { ...GregorianCalendarLocale };
locale.lang = {
  placeholder: 'Select date',
  rangePlaceholder: ['Start date', 'End date'],
  ...CalendarLocale, // 因为这里不包含timezoneOffset,就不展示这部分的内容了
};

locale.timePickerLocale = { ...TimePickerLocale };

export default locale;

Last updated