From 4f494e5abccfe4e1fcde5b59745188f384a27652 Mon Sep 17 00:00:00 2001
From: mwplay <tqj.zyy@gmail.com>
Date: Fri, 13 Dec 2024 15:53:31 +0800
Subject: [PATCH 1/2] add timezone change button

---
 .../BrowserCell/BrowserCell.react.js          | 11 ++-
 src/components/BrowserRow/BrowserRow.react.js | 14 ++++
 .../DateTimeEditor/DateTimeEditor.react.js    | 71 +++++++------------
 .../TimeZoneToggle/TimeZoneToggle.react.js    | 20 ++++++
 .../TimeZoneToggle/TimeZoneToggle.scss        | 49 +++++++++++++
 .../Data/Browser/BrowserTable.react.js        |  4 ++
 .../Data/Browser/BrowserToolbar.react.js      | 11 ++-
 .../Data/Browser/DataBrowser.react.js         | 52 ++++++++++++--
 src/lib/DateUtils.js                          | 38 +++++++---
 9 files changed, 207 insertions(+), 63 deletions(-)
 create mode 100644 src/components/TimeZoneToggle/TimeZoneToggle.react.js
 create mode 100644 src/components/TimeZoneToggle/TimeZoneToggle.scss

diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js
index 74614feb69..072bc3c98b 100644
--- a/src/components/BrowserCell/BrowserCell.react.js
+++ b/src/components/BrowserCell/BrowserCell.react.js
@@ -7,7 +7,7 @@
  */
 import * as Filters from 'lib/Filters';
 import { List, Map } from 'immutable';
-import { dateStringUTC } from 'lib/DateUtils';
+import { yearMonthDayTimeFormatter } from 'lib/DateUtils';
 import getFileName from 'lib/getFileName';
 import Parse from 'parse';
 import Pill from 'components/Pill/Pill.react';
@@ -152,7 +152,11 @@ export default class BrowserCell extends Component {
       } else if (typeof value === 'string') {
         this.props.value = new Date(this.props.value);
       }
-      this.copyableValue = content = dateStringUTC(this.props.value);
+      if (this.props.useLocalTime) {
+        this.copyableValue = content = yearMonthDayTimeFormatter(this.props.value, false);
+      } else {
+        this.copyableValue = content = this.props.value.toISOString();
+      }
     } else if (this.props.type === 'Boolean') {
       this.copyableValue = content = this.props.value ? 'True' : 'False';
     } else if (this.props.type === 'Object' || this.props.type === 'Bytes') {
@@ -222,6 +226,9 @@ export default class BrowserCell extends Component {
         ?.then(() => this.renderCellContent())
         ?.catch(err => console.log(err));
     }
+    if (this.props.useLocalTime !== prevProps.useLocalTime && this.props.type === 'Date') {
+      this.renderCellContent();
+    }
     if (this.props.current) {
       if (prevProps.selectedCells === this.props.selectedCells) {
         const node = this.cellRef.current;
diff --git a/src/components/BrowserRow/BrowserRow.react.js b/src/components/BrowserRow/BrowserRow.react.js
index 0e6f40a694..7a0fc776d3 100644
--- a/src/components/BrowserRow/BrowserRow.react.js
+++ b/src/components/BrowserRow/BrowserRow.react.js
@@ -1,6 +1,7 @@
 import Parse from 'parse';
 import encode from 'parse/lib/browser/encode';
 import React, { Component } from 'react';
+import { formatDateTime } from 'lib/DateUtils';
 
 import BrowserCell from 'components/BrowserCell/BrowserCell.react';
 import styles from 'dashboard/Data/Browser/Browser.scss';
@@ -19,6 +20,18 @@ export default class BrowserRow extends Component {
     return isRefDifferent ? JSON.stringify(obj) !== JSON.stringify(nextObj) : isRefDifferent;
   }
 
+  renderField(name, type, value, targetClass) {
+    if (!value) {
+      return '';
+    }
+    
+    switch(type) {
+      case 'Date':
+        return formatDateTime(value, this.props.useLocalTime);
+      // ... 其他 case
+    }
+  }
+
   render() {
     const {
       className,
@@ -159,6 +172,7 @@ export default class BrowserRow extends Component {
               setShowAggregatedData={this.props.setShowAggregatedData}
               setErrorAggregatedData={this.props.setErrorAggregatedData}
               firstSelectedCell={this.props.firstSelectedCell}
+              useLocalTime={this.props.useLocalTime}
             />
           );
         })}
diff --git a/src/components/DateTimeEditor/DateTimeEditor.react.js b/src/components/DateTimeEditor/DateTimeEditor.react.js
index 3799d845e0..dac706af9e 100644
--- a/src/components/DateTimeEditor/DateTimeEditor.react.js
+++ b/src/components/DateTimeEditor/DateTimeEditor.react.js
@@ -9,42 +9,23 @@ import DateTimePicker from 'components/DateTimePicker/DateTimePicker.react';
 import hasAncestor from 'lib/hasAncestor';
 import React from 'react';
 import styles from 'components/DateTimeEditor/DateTimeEditor.scss';
+import { yearMonthDayTimeFormatter } from 'lib/DateUtils';
 
 export default class DateTimeEditor extends React.Component {
   constructor(props) {
-    super();
-
+    super(props);
     this.state = {
-      open: false,
-      position: null,
       value: props.value,
       text: props.value.toISOString(),
+      open: false
     };
-
-    this.checkExternalClick = this.checkExternalClick.bind(this);
-    this.handleKey = this.handleKey.bind(this);
-    this.inputRef = React.createRef();
     this.editorRef = React.createRef();
-  }
-
-  componentWillReceiveProps(props) {
-    this.setState({ value: props.value, text: props.value.toISOString() });
+    this.inputRef = React.createRef();
   }
 
   componentDidMount() {
-    document.body.addEventListener('click', this.checkExternalClick);
-    this.inputRef.current.addEventListener('keypress', this.handleKey);
-  }
-
-  componentWillUnmount() {
-    document.body.removeEventListener('click', this.checkExternalClick);
-    this.inputRef.current.removeEventListener('keypress', this.handleKey);
-  }
-
-  checkExternalClick(e) {
-    if (!hasAncestor(e.target, this.editorRef.current)) {
-      this.props.onCommit(this.state.value);
-    }
+    this.inputRef.current.focus();
+    this.inputRef.current.select();
   }
 
   handleKey(e) {
@@ -70,25 +51,21 @@ export default class DateTimeEditor extends React.Component {
     if (isNaN(date.getTime())) {
       this.setState({
         value: this.props.value,
-        text: this.props.value.toISOString(),
+        text: this.props.value.toISOString()
       });
     } else {
-      if (this.state.text.endsWith('Z')) {
-        this.setState({ value: date });
-      } else {
-        const utc = new Date(
-          Date.UTC(
-            date.getFullYear(),
-            date.getMonth(),
-            date.getDate(),
-            date.getHours(),
-            date.getMinutes(),
-            date.getSeconds(),
-            date.getMilliseconds()
-          )
-        );
-        this.setState({ value: utc });
-      }
+      const utc = new Date(
+        Date.UTC(
+          date.getFullYear(),
+          date.getMonth(),
+          date.getDate(),
+          date.getHours(),
+          date.getMinutes(),
+          date.getSeconds(),
+          date.getMilliseconds()
+        )
+      );
+      this.setState({ value: utc });
     }
   }
 
@@ -100,10 +77,11 @@ export default class DateTimeEditor extends React.Component {
           <DateTimePicker
             value={this.state.value}
             width={240}
-            onChange={value => this.setState({ value: value, text: value.toISOString() })}
-            close={() =>
-              this.setState({ open: false }, () => this.props.onCommit(this.state.value))
-            }
+            local={false}
+            onChange={value => this.setState({ value, text: value.toISOString() })}
+            close={() => {
+              this.setState({ open: false }, () => this.props.onCommit(this.state.value));
+            }}
           />
         </div>
       );
@@ -120,6 +98,7 @@ export default class DateTimeEditor extends React.Component {
           onClick={this.toggle.bind(this)}
           onChange={this.inputDate.bind(this)}
           onBlur={this.commitDate.bind(this)}
+          onKeyDown={this.handleKey.bind(this)}
         />
         {popover}
       </div>
diff --git a/src/components/TimeZoneToggle/TimeZoneToggle.react.js b/src/components/TimeZoneToggle/TimeZoneToggle.react.js
new file mode 100644
index 0000000000..3f01234053
--- /dev/null
+++ b/src/components/TimeZoneToggle/TimeZoneToggle.react.js
@@ -0,0 +1,20 @@
+import React from 'react';
+import styles from './TimeZoneToggle.scss';
+
+const TimeZoneToggle = ({ value, onChange }) => {
+  const switchStyle = {
+    backgroundColor: value ? 'rgb(0, 219, 124)' : 'rgb(204, 204, 204)',
+    transition: 'background-color 0.15s ease-out'
+  };
+
+  return (
+    <div className={styles.container} onClick={() => onChange(!value)}>
+      <div className={`${styles.switch} ${value ? styles.right : styles.left}`} style={switchStyle}>
+        <span className={styles.option}>{value ? 'Local' : 'UTC'}</span>
+        <span className={styles.slider} />
+      </div>
+    </div>
+  );
+};
+
+export default TimeZoneToggle; 
\ No newline at end of file
diff --git a/src/components/TimeZoneToggle/TimeZoneToggle.scss b/src/components/TimeZoneToggle/TimeZoneToggle.scss
new file mode 100644
index 0000000000..05aa6e31c0
--- /dev/null
+++ b/src/components/TimeZoneToggle/TimeZoneToggle.scss
@@ -0,0 +1,49 @@
+.container {
+  display: inline-block;
+  cursor: pointer;
+  height: 30px;
+  line-height: 30px;
+}
+
+.switch {
+  position: relative;
+  display: flex;
+  align-items: center;
+  width: 50px;
+  height: 18px;
+  border-radius: 12px;
+  margin: 6px 8px 0 8px;
+}
+
+.option {
+  position: absolute;
+  width: 30px;
+  text-align: center;
+  color: white;
+  font-size: 10px;
+  z-index: 1;
+  line-height: 18px;
+}
+
+.left .option {
+  left: 18px;
+}
+
+.right .option {
+  left: 2px;
+}
+
+.slider {
+  position: absolute;
+  width: 16px;
+  height: 16px;
+  background: white;
+  border-radius: 50%;
+  left: 2px;
+  transition: transform 0.15s ease-out;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
+}
+
+.right .slider {
+  transform: translateX(30px);
+}
\ No newline at end of file
diff --git a/src/dashboard/Data/Browser/BrowserTable.react.js b/src/dashboard/Data/Browser/BrowserTable.react.js
index 59dc53d5da..f8699d96ea 100644
--- a/src/dashboard/Data/Browser/BrowserTable.react.js
+++ b/src/dashboard/Data/Browser/BrowserTable.react.js
@@ -16,6 +16,7 @@ import React from 'react';
 import styles from 'dashboard/Data/Browser/Browser.scss';
 import Button from 'components/Button/Button.react';
 import { CurrentApp } from 'context/currentApp';
+import { formatDateTime } from 'lib/DateUtils';
 
 const MAX_ROWS = 200; // Number of rows to render at any time
 const ROWS_OFFSET = 160;
@@ -218,6 +219,7 @@ export default class BrowserTable extends React.Component {
                     setShowAggregatedData={this.props.setShowAggregatedData}
                     setErrorAggregatedData={this.props.setErrorAggregatedData}
                     firstSelectedCell={this.props.firstSelectedCell}
+                    useLocalTime={this.props.useLocalTime}
                   />
                   <Button
                     value="Clone"
@@ -298,6 +300,7 @@ export default class BrowserTable extends React.Component {
               setShowAggregatedData={this.props.setShowAggregatedData}
               setErrorAggregatedData={this.props.setErrorAggregatedData}
               firstSelectedCell={this.props.firstSelectedCell}
+              useLocalTime={this.props.useLocalTime}
             />
             <Button
               value="Add"
@@ -387,6 +390,7 @@ export default class BrowserTable extends React.Component {
             setShowAggregatedData={this.props.setShowAggregatedData}
             setErrorAggregatedData={this.props.setErrorAggregatedData}
             firstSelectedCell={this.props.firstSelectedCell}
+            useLocalTime={this.props.useLocalTime}
           />
         );
       }
diff --git a/src/dashboard/Data/Browser/BrowserToolbar.react.js b/src/dashboard/Data/Browser/BrowserToolbar.react.js
index b8e44cc038..b32afea249 100644
--- a/src/dashboard/Data/Browser/BrowserToolbar.react.js
+++ b/src/dashboard/Data/Browser/BrowserToolbar.react.js
@@ -19,6 +19,7 @@ import ColumnsConfiguration from 'components/ColumnsConfiguration/ColumnsConfigu
 import SecureFieldsDialog from 'dashboard/Data/Browser/SecureFieldsDialog.react';
 import LoginDialog from 'dashboard/Data/Browser/LoginDialog.react';
 import Toggle from 'components/Toggle/Toggle.react';
+import TimeZoneToggle from 'components/TimeZoneToggle/TimeZoneToggle.react';
 
 const BrowserToolbar = ({
   className,
@@ -79,7 +80,10 @@ const BrowserToolbar = ({
 
   togglePanel,
   isPanelVisible,
-  classwiseCloudFunctions
+  classwiseCloudFunctions,
+
+  useLocalTime,
+  onToggleTimeZone
 }) => {
   const selectionLength = Object.keys(selection).length;
   const isPendingEditCloneRows = editCloneRows && editCloneRows.length > 0;
@@ -439,6 +443,11 @@ const BrowserToolbar = ({
           <MenuItem text={'Cancel all pending rows'} onClick={onCancelPendingEditRows} />
         </BrowserMenu>
       )}
+       <div className={styles.toolbarSeparator} />
+      <div style={{ display: 'inline-flex', alignItems: 'center', marginRight: '10px' }}>
+<span style={{ marginRight: '8px', fontSize: '12px', color: '#ffffff' }}>TimeZone</span>
+<TimeZoneToggle value={useLocalTime} onChange={onToggleTimeZone} />
+</div>
     </Toolbar>
   );
 };
diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js
index 90a5dd8347..0ede40bc5e 100644
--- a/src/dashboard/Data/Browser/DataBrowser.react.js
+++ b/src/dashboard/Data/Browser/DataBrowser.react.js
@@ -5,6 +5,7 @@
  * This source code is licensed under the license found in the LICENSE file in
  * the root directory of this source tree.
  */
+import { List } from 'immutable';
 import ContextMenu from 'components/ContextMenu/ContextMenu.react';
 import copy from 'copy-to-clipboard';
 import BrowserTable from 'dashboard/Data/Browser/BrowserTable.react';
@@ -32,15 +33,37 @@ export default class DataBrowser extends React.Component {
       props.className,
       columnPreferences[props.className]
     );
+
+    const simplifiedSchema = props.schema ? 
+      this.getSimplifiedSchema(props.schema, props.className) : {};
+    const allClassesSchema = (props.schema && props.classes) ? 
+      this.getAllClassesSchema(props.schema, props.classes) : {};
+
     this.state = {
       order: order,
       current: null,
       editing: false,
       copyableValue: undefined,
       selectedObjectId: undefined,
-      simplifiedSchema: this.getSimplifiedSchema(props.schema, props.className),
-      allClassesSchema: this.getAllClassesSchema(props.schema, props.classes),
-      isPanelVisible: false,
+      simplifiedSchema,
+      allClassesSchema,
+      selection: {},
+      data: null,
+      lastMax: -1,
+      totalCount: 0,
+      lastError: null,
+      relation: null,
+      isUnique: false,
+      uniqueField: null,
+      filters: new List(),
+      ordering: ColumnPreferences.getColumnSort(
+        props.className,
+        props.columns,
+        props.app.applicationId,
+        order
+      ),
+      editCloneRows: [],
+      isMultiselect: false,
       selectedCells: { list: new Set(), rowStart: -1, rowEnd: -1, colStart: -1, colEnd: -1 },
       firstSelectedCell: null,
       selectedData: [],
@@ -48,7 +71,11 @@ export default class DataBrowser extends React.Component {
       panelWidth: 300,
       isResizing: false,
       maxWidth: window.innerWidth - 300,
+      columnSizeControlled: false,
       showAggregatedData: true,
+      errorAggregatedData: null,
+      isPanelVisible: false,
+      useLocalTime: localStorage.getItem('parse_dashboard_useLocalTime') === 'true',
     };
 
     this.handleResizeDiv = this.handleResizeDiv.bind(this);
@@ -202,9 +229,14 @@ export default class DataBrowser extends React.Component {
     }
   }
 
-  getAllClassesSchema(schema) {
+  getAllClassesSchema(schema, classes) {
+    if (!schema || !schema.data || !schema.data.get('classes') || !classes) {
+      return {};
+    }
+    
     const allClasses = Object.keys(schema.data.get('classes').toObject());
     const schemaSimplifiedData = {};
+    
     allClasses.forEach(className => {
       const classSchema = schema.data.get('classes').get(className);
       if (classSchema) {
@@ -216,7 +248,6 @@ export default class DataBrowser extends React.Component {
           };
         });
       }
-      return schemaSimplifiedData;
     });
     return schemaSimplifiedData;
   }
@@ -545,6 +576,14 @@ export default class DataBrowser extends React.Component {
     }
   }
 
+  toggleTimeZone = () => {
+    this.setState(prevState => {
+      const newValue = !prevState.useLocalTime;
+      localStorage.setItem('parse_dashboard_useLocalTime', newValue);
+      return { useLocalTime: newValue };
+    });
+  }
+
   render() {
     const {
       className,
@@ -585,6 +624,7 @@ export default class DataBrowser extends React.Component {
             isResizing={this.state.isResizing}
             setShowAggregatedData={this.setShowAggregatedData}
             firstSelectedCell={this.state.firstSelectedCell}
+            useLocalTime={this.state.useLocalTime}
             {...other}
           />
           {this.state.isPanelVisible && (
@@ -642,6 +682,8 @@ export default class DataBrowser extends React.Component {
           allClassesSchema={this.state.allClassesSchema}
           togglePanel={this.togglePanelVisibility}
           isPanelVisible={this.state.isPanelVisible}
+          useLocalTime={this.state.useLocalTime}
+          onToggleTimeZone={this.toggleTimeZone}
           {...other}
         />
 
diff --git a/src/lib/DateUtils.js b/src/lib/DateUtils.js
index 57163eafe0..ca279b3681 100644
--- a/src/lib/DateUtils.js
+++ b/src/lib/DateUtils.js
@@ -141,19 +141,20 @@ export function yearMonthDayFormatter(date) {
   });
 }
 
-export function yearMonthDayTimeFormatter(date, timeZone) {
+export function yearMonthDayTimeFormatter(date, useUTC) {
   const options = {
     year: 'numeric',
-    month: 'short',
-    day: 'numeric',
-    hour: 'numeric',
-    minute: 'numeric',
+    month: '2-digit',
+    day: '2-digit',
+    hour: '2-digit',
+    minute: '2-digit',
+    second: '2-digit',
     hour12: false,
+    timeZone: useUTC ? 'UTC' : undefined
   };
-  if (timeZone) {
-    options.timeZoneName = 'short';
-  }
-  return date.toLocaleDateString('en-US', options);
+  return date.toLocaleString('en-US', options)
+    .replace(',', '')  // 移除日期和时间之间的逗号
+    .replace(/(\d+)\/(\d+)\/(\d+)/, '$3-$1-$2'); // 转换为 yyyy-MM-dd 格式
 }
 
 export function getDateMethod(local, methodName) {
@@ -171,3 +172,22 @@ export function pad(number) {
   }
   return r;
 }
+
+export function formatDateTime(date, useLocalTime = false) {
+  if (!date) return '';
+  
+  const d = new Date(date);
+  
+  // 根据useLocalTime决定使用本地时间还是UTC时间
+  const year = useLocalTime ? d.getFullYear() : d.getUTCFullYear();
+  const month = (useLocalTime ? d.getMonth() : d.getUTCMonth()) + 1;
+  const day = useLocalTime ? d.getDate() : d.getUTCDate();
+  const hours = useLocalTime ? d.getHours() : d.getUTCHours();
+  const minutes = useLocalTime ? d.getMinutes() : d.getUTCMinutes();
+  const seconds = useLocalTime ? d.getSeconds() : d.getUTCSeconds();
+
+  // 补零函数
+  const pad = (num) => String(num).padStart(2, '0');
+
+  return `${year}-${pad(month)}-${pad(day)} ${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
+}

From c7bece882f3957bfa7ff7e9a9cac2f2afc48be9f Mon Sep 17 00:00:00 2001
From: mwplay <tqj.zyy@gmail.com>
Date: Fri, 13 Dec 2024 16:12:31 +0800
Subject: [PATCH 2/2] refactor: Update comments for clarity and add tests for
 date formatting functions

---
 src/components/BrowserRow/BrowserRow.react.js |  2 +-
 .../TimeZoneToggle/TimeZoneToggle.test.js     | 37 ++++++++++++++++
 .../Data/Browser/DataBrowser.test.js          | 39 +++++++++++++++++
 src/lib/DateUtils.js                          |  8 ++--
 src/lib/tests/DateUtils.test.js               | 42 +++++++++++++++++++
 5 files changed, 123 insertions(+), 5 deletions(-)
 create mode 100644 src/components/TimeZoneToggle/TimeZoneToggle.test.js
 create mode 100644 src/dashboard/Data/Browser/DataBrowser.test.js

diff --git a/src/components/BrowserRow/BrowserRow.react.js b/src/components/BrowserRow/BrowserRow.react.js
index 7a0fc776d3..6ce1554bdc 100644
--- a/src/components/BrowserRow/BrowserRow.react.js
+++ b/src/components/BrowserRow/BrowserRow.react.js
@@ -28,7 +28,7 @@ export default class BrowserRow extends Component {
     switch(type) {
       case 'Date':
         return formatDateTime(value, this.props.useLocalTime);
-      // ... 其他 case
+      // ... other cases
     }
   }
 
diff --git a/src/components/TimeZoneToggle/TimeZoneToggle.test.js b/src/components/TimeZoneToggle/TimeZoneToggle.test.js
new file mode 100644
index 0000000000..d4c28433fb
--- /dev/null
+++ b/src/components/TimeZoneToggle/TimeZoneToggle.test.js
@@ -0,0 +1,37 @@
+import React from 'react';
+import { mount } from 'enzyme';
+import TimeZoneToggle from '../TimeZoneToggle.react';
+
+describe('TimeZoneToggle', () => {
+  it('should render UTC text when value is false', () => {
+    const wrapper = mount(<TimeZoneToggle value={false} onChange={() => {}} />);
+    expect(wrapper.find('.option').text()).toBe('UTC');
+    expect(wrapper.find('.switch').hasClass('left')).toBe(true);
+  });
+
+  it('should render Local text when value is true', () => {
+    const wrapper = mount(<TimeZoneToggle value={true} onChange={() => {}} />);
+    expect(wrapper.find('.option').text()).toBe('Local');
+    expect(wrapper.find('.switch').hasClass('right')).toBe(true);
+  });
+
+  it('should call onChange with opposite value when clicked', () => {
+    const onChange = jest.fn();
+    const wrapper = mount(<TimeZoneToggle value={false} onChange={onChange} />);
+    
+    wrapper.find('.container').simulate('click');
+    expect(onChange).toHaveBeenCalledWith(true);
+
+    wrapper.setProps({ value: true });
+    wrapper.find('.container').simulate('click');
+    expect(onChange).toHaveBeenCalledWith(false);
+  });
+
+  it('should have correct background color based on value', () => {
+    const wrapper = mount(<TimeZoneToggle value={false} onChange={() => {}} />);
+    expect(wrapper.find('.switch').prop('style').backgroundColor).toBe('rgb(204, 204, 204)');
+
+    wrapper.setProps({ value: true });
+    expect(wrapper.find('.switch').prop('style').backgroundColor).toBe('rgb(0, 219, 124)');
+  });
+}); 
\ No newline at end of file
diff --git a/src/dashboard/Data/Browser/DataBrowser.test.js b/src/dashboard/Data/Browser/DataBrowser.test.js
new file mode 100644
index 0000000000..fa99473ad3
--- /dev/null
+++ b/src/dashboard/Data/Browser/DataBrowser.test.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import { mount } from 'enzyme';
+import DataBrowser from '../DataBrowser.react';
+
+describe('DataBrowser TimeZone Feature', () => {
+  let wrapper;
+  const mockLocalStorage = {
+    getItem: jest.fn(),
+    setItem: jest.fn(),
+  };
+
+  beforeEach(() => {
+    global.localStorage = mockLocalStorage;
+  });
+
+  it('should initialize with stored timezone preference', () => {
+    mockLocalStorage.getItem.mockReturnValue('true');
+    wrapper = mount(<DataBrowser {...defaultProps} />);
+    expect(wrapper.state().useLocalTime).toBe(true);
+  });
+
+  it('should toggle timezone and save preference', () => {
+    wrapper = mount(<DataBrowser {...defaultProps} />);
+    
+    wrapper.instance().toggleTimeZone();
+    expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
+      'parse_dashboard_useLocalTime',
+      'true'
+    );
+    expect(wrapper.state().useLocalTime).toBe(true);
+
+    wrapper.instance().toggleTimeZone();
+    expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
+      'parse_dashboard_useLocalTime',
+      'false'
+    );
+    expect(wrapper.state().useLocalTime).toBe(false);
+  });
+}); 
\ No newline at end of file
diff --git a/src/lib/DateUtils.js b/src/lib/DateUtils.js
index ca279b3681..871c7f02d0 100644
--- a/src/lib/DateUtils.js
+++ b/src/lib/DateUtils.js
@@ -153,8 +153,8 @@ export function yearMonthDayTimeFormatter(date, useUTC) {
     timeZone: useUTC ? 'UTC' : undefined
   };
   return date.toLocaleString('en-US', options)
-    .replace(',', '')  // 移除日期和时间之间的逗号
-    .replace(/(\d+)\/(\d+)\/(\d+)/, '$3-$1-$2'); // 转换为 yyyy-MM-dd 格式
+    .replace(',', '')  // Remove comma between date and time
+    .replace(/(\d+)\/(\d+)\/(\d+)/, '$3-$1-$2'); // Convert to yyyy-MM-dd format
 }
 
 export function getDateMethod(local, methodName) {
@@ -178,7 +178,7 @@ export function formatDateTime(date, useLocalTime = false) {
   
   const d = new Date(date);
   
-  // 根据useLocalTime决定使用本地时间还是UTC时间
+  // Determine whether to use local time or UTC time based on useLocalTime flag
   const year = useLocalTime ? d.getFullYear() : d.getUTCFullYear();
   const month = (useLocalTime ? d.getMonth() : d.getUTCMonth()) + 1;
   const day = useLocalTime ? d.getDate() : d.getUTCDate();
@@ -186,7 +186,7 @@ export function formatDateTime(date, useLocalTime = false) {
   const minutes = useLocalTime ? d.getMinutes() : d.getUTCMinutes();
   const seconds = useLocalTime ? d.getSeconds() : d.getUTCSeconds();
 
-  // 补零函数
+  // Pad numbers with leading zeros
   const pad = (num) => String(num).padStart(2, '0');
 
   return `${year}-${pad(month)}-${pad(day)} ${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
diff --git a/src/lib/tests/DateUtils.test.js b/src/lib/tests/DateUtils.test.js
index 1b667ffeea..78ba198f75 100644
--- a/src/lib/tests/DateUtils.test.js
+++ b/src/lib/tests/DateUtils.test.js
@@ -65,3 +65,45 @@ describe('daysInMonth', () => {
     expect(DateUtils.daysInMonth(new Date(2015, 8))).toBe(30);
   });
 });
+
+describe('DateUtils', () => {
+  describe('formatDateTime', () => {
+    const testDate = new Date('2024-03-21T08:00:00.000Z');
+
+    it('should format date in UTC time when useLocalTime is false', () => {
+      const result = DateUtils.formatDateTime(testDate, false);
+      expect(result).toBe('2024-03-21 08:00:00');
+    });
+
+    it('should format date in local time when useLocalTime is true', () => {
+      // 假设本地时区是 UTC+8
+      const result = DateUtils.formatDateTime(testDate, true);
+      expect(result).toBe('2024-03-21 16:00:00');
+    });
+
+    it('should handle null date', () => {
+      const result = DateUtils.formatDateTime(null, false);
+      expect(result).toBe('');
+    });
+
+    it('should handle invalid date', () => {
+      const result = DateUtils.formatDateTime('invalid date', false);
+      expect(result).toBe('');
+    });
+  });
+
+  describe('yearMonthDayTimeFormatter', () => {
+    const testDate = new Date('2024-03-21T08:00:00.000Z');
+
+    it('should format date in UTC format', () => {
+      const result = DateUtils.yearMonthDayTimeFormatter(testDate, true);
+      expect(result).toBe('2024-03-21 08:00:00');
+    });
+
+    it('should format date in local format', () => {
+      const result = DateUtils.yearMonthDayTimeFormatter(testDate, false);
+      // 假设本地时区是 UTC+8
+      expect(result).toBe('2024-03-21 16:00:00');
+    });
+  });
+});