antd 1.x datepicker timezoneOffset debug

Antd(1.x stable) - DatePicker

依赖关系

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

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

    const DatePicker = wrapPicker(createPicker(RcCalendar));
    const MonthPicker = wrapPicker(createPicker(MonthCalendar), 'yyyy-MM');
    
    1
    2
  • 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,
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /*
     * zh-cn locale
     * @ignore
     * @author yiminghe@gmail.com
     */
    module.exports = {
      // in minutes
      timezoneOffset: 8 * 60,
      firstDayOfWeek: 1,
      minimalDaysInFirstWeek: 1,
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

代码逻辑分析

  • 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;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  • createPicker将props传入的locale,传递给GregorianCalendar对象生成基本的locale信息

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

    <TheCalendar
      ...
      formatter={props.getFormatter()}
      locale={locale.lang}
      ... />
    
    1
    2
    3
    4
    5

核心问题

  • 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;
    }
    
    1
    2
    3
    4

深入LocaleProvider的配置

LocaleProvider的使用

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

...

return <LocaleProvider locale={enUS}><App /></LocaleProvider>;
1
2
3
4
5

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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28