Jelajahi Sumber

feat: rewrite date logic (#2390)

* chore: remove unused fields

* chore: rewrite date logic

* chore: apply suggestions from Alex

* chore: add space in date format

* chore: re-add error handling in apply-changeset
Richard Shiue 2 tahun lalu
induk
melakukan
376f2d887d
21 mengubah file dengan 764 tambahan dan 410 penghapusan
  1. 6 11
      frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_data_persistence.dart
  2. 1 2
      frontend/appflowy_flutter/lib/plugins/database_view/application/database_controller.dart
  3. 0 4
      frontend/appflowy_flutter/lib/plugins/database_view/application/field/type_option/date_bloc.dart
  4. 88 114
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cal_bloc.dart
  5. 5 1
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell_bloc.dart
  6. 42 74
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart
  7. 2 4
      frontend/appflowy_flutter/lib/plugins/trash/src/trash_cell.dart
  8. 64 3
      frontend/rust-lib/Cargo.lock
  9. 1 0
      frontend/rust-lib/flowy-database2/Cargo.toml
  10. 5 7
      frontend/rust-lib/flowy-database2/src/entities/type_option_entities/date_entities.rs
  11. 6 6
      frontend/rust-lib/flowy-database2/src/event_handler.rs
  12. 12 14
      frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs
  13. 34 15
      frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs
  14. 341 82
      frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_tests.rs
  15. 130 62
      frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs
  16. 22 5
      frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option_entities.rs
  17. 3 2
      frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_tests.rs
  18. 1 1
      frontend/rust-lib/flowy-database2/tests/database/database_editor.rs
  19. 1 1
      frontend/rust-lib/flowy-database2/tests/database/field_test/util.rs
  20. 0 1
      frontend/rust-lib/flowy-database2/tests/database/mock_data/board_mock_data.rs
  21. 0 1
      frontend/rust-lib/flowy-database2/tests/database/mock_data/grid_mock_data.rs

+ 6 - 11
frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_data_persistence.dart

@@ -29,7 +29,7 @@ class TextCellDataPersistence implements CellDataPersistence<String> {
 @freezed
 @freezed
 class DateCellData with _$DateCellData {
 class DateCellData with _$DateCellData {
   const factory DateCellData({
   const factory DateCellData({
-    required DateTime date,
+    DateTime? dateTime,
     String? time,
     String? time,
     required bool includeTime,
     required bool includeTime,
   }) = _DateCellData;
   }) = _DateCellData;
@@ -45,19 +45,14 @@ class DateCellDataPersistence implements CellDataPersistence<DateCellData> {
   Future<Option<FlowyError>> save(DateCellData data) {
   Future<Option<FlowyError>> save(DateCellData data) {
     var payload = DateChangesetPB.create()..cellPath = _makeCellPath(cellId);
     var payload = DateChangesetPB.create()..cellPath = _makeCellPath(cellId);
 
 
-    // This is a bit of a hack. This converts the data.date which is in
-    // UTC to Local but actually changes the timestamp instead of just
-    // changing the isUtc flag
-    final dateTime = DateTime(data.date.year, data.date.month, data.date.day);
-
-    final date = (dateTime.millisecondsSinceEpoch ~/ 1000).toString();
-    payload.date = date;
-    payload.isUtc = data.date.isUtc;
-    payload.includeTime = data.includeTime;
-
+    if (data.dateTime != null) {
+      final date = (data.dateTime!.millisecondsSinceEpoch ~/ 1000).toString();
+      payload.date = date;
+    }
     if (data.time != null) {
     if (data.time != null) {
       payload.time = data.time!;
       payload.time = data.time!;
     }
     }
+    payload.includeTime = data.includeTime;
 
 
     return DatabaseEventUpdateDateCell(payload).send().then((result) {
     return DatabaseEventUpdateDateCell(payload).send().then((result) {
       return result.fold(
       return result.fold(

+ 1 - 2
frontend/appflowy_flutter/lib/plugins/database_view/application/database_controller.dart

@@ -342,10 +342,9 @@ class RowDataBuilder {
     _cellDataByFieldId[fieldInfo.field.id] = num.toString();
     _cellDataByFieldId[fieldInfo.field.id] = num.toString();
   }
   }
 
 
-  /// The date should use the UTC timezone. Becuase the backend uses UTC timezone to format the time string.
   void insertDate(FieldInfo fieldInfo, DateTime date) {
   void insertDate(FieldInfo fieldInfo, DateTime date) {
     assert(fieldInfo.fieldType == FieldType.DateTime);
     assert(fieldInfo.fieldType == FieldType.DateTime);
-    final timestamp = (date.toUtc().millisecondsSinceEpoch ~/ 1000);
+    final timestamp = date.millisecondsSinceEpoch ~/ 1000;
     _cellDataByFieldId[fieldInfo.field.id] = timestamp.toString();
     _cellDataByFieldId[fieldInfo.field.id] = timestamp.toString();
   }
   }
 
 

+ 0 - 4
frontend/appflowy_flutter/lib/plugins/database_view/application/field/type_option/date_bloc.dart

@@ -53,10 +53,6 @@ class DateTypeOptionBloc
       if (timeFormat != null) {
       if (timeFormat != null) {
         typeOption.timeFormat = timeFormat;
         typeOption.timeFormat = timeFormat;
       }
       }
-
-      if (includeTime != null) {
-        typeOption.includeTime = includeTime;
-      }
     });
     });
   }
   }
 }
 }

+ 88 - 114
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cal_bloc.dart

@@ -12,7 +12,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:table_calendar/table_calendar.dart';
 import 'package:table_calendar/table_calendar.dart';
 import 'dart:async';
 import 'dart:async';
-import 'package:dartz/dartz.dart';
 import 'package:protobuf/protobuf.dart';
 import 'package:protobuf/protobuf.dart';
 
 
 part 'date_cal_bloc.freezed.dart';
 part 'date_cal_bloc.freezed.dart';
@@ -31,45 +30,39 @@ class DateCellCalendarBloc
       (event, emit) async {
       (event, emit) async {
         await event.when(
         await event.when(
           initial: () async => _startListening(),
           initial: () async => _startListening(),
-          selectDay: (date) async {
-            await _updateDateData(emit, date: date, time: state.time);
-          },
-          setCalFormat: (format) {
-            emit(state.copyWith(format: format));
-          },
-          setFocusedDay: (focusedDay) {
-            emit(state.copyWith(focusedDay: focusedDay));
-          },
           didReceiveCellUpdate: (DateCellDataPB? cellData) {
           didReceiveCellUpdate: (DateCellDataPB? cellData) {
-            final dateCellData = calDataFromCellData(cellData);
-            final time = dateCellData.foldRight(
-              "",
-              (dateData, previous) => dateData.time ?? '',
+            final dateData = _dateDataFromCellData(cellData);
+            emit(
+              state.copyWith(
+                dateTime: dateData.dateTime,
+                time: dateData.time,
+                includeTime: dateData.includeTime,
+              ),
             );
             );
-            emit(state.copyWith(dateCellData: dateCellData, time: time));
+          },
+          didReceiveTimeFormatError: (String? timeFormatError) {
+            emit(state.copyWith(timeFormatError: timeFormatError));
+          },
+          selectDay: (date) async {
+            await _updateDateData(emit, date: date);
           },
           },
           setIncludeTime: (includeTime) async {
           setIncludeTime: (includeTime) async {
             await _updateDateData(emit, includeTime: includeTime);
             await _updateDateData(emit, includeTime: includeTime);
           },
           },
+          setTime: (time) async {
+            await _updateDateData(emit, time: time);
+          },
           setDateFormat: (dateFormat) async {
           setDateFormat: (dateFormat) async {
             await _updateTypeOption(emit, dateFormat: dateFormat);
             await _updateTypeOption(emit, dateFormat: dateFormat);
           },
           },
           setTimeFormat: (timeFormat) async {
           setTimeFormat: (timeFormat) async {
             await _updateTypeOption(emit, timeFormat: timeFormat);
             await _updateTypeOption(emit, timeFormat: timeFormat);
           },
           },
-          setTime: (time) async {
-            if (state.dateCellData.isSome()) {
-              await _updateDateData(emit, time: time);
-            }
+          setCalFormat: (format) {
+            emit(state.copyWith(format: format));
           },
           },
-          didUpdateCalData:
-              (Option<DateCellData> data, Option<String> timeFormatError) {
-            emit(
-              state.copyWith(
-                dateCellData: data,
-                timeFormatError: timeFormatError,
-              ),
-            );
+          setFocusedDay: (focusedDay) {
+            emit(state.copyWith(focusedDay: focusedDay));
           },
           },
         );
         );
       },
       },
@@ -81,65 +74,44 @@ class DateCellCalendarBloc
     DateTime? date,
     DateTime? date,
     String? time,
     String? time,
     bool? includeTime,
     bool? includeTime,
-  }) {
-    final DateCellData newDateData = state.dateCellData.fold(
-      () => DateCellData(
-        date: date ?? DateTime.now(),
-        time: time,
-        includeTime: includeTime ?? false,
-      ),
-      (dateData) {
-        var newDateData = dateData;
-        if (date != null && !isSameDay(newDateData.date, date)) {
-          newDateData = newDateData.copyWith(date: date);
-        }
-
-        if (newDateData.time != time) {
-          newDateData = newDateData.copyWith(time: time);
-        }
-
-        if (includeTime != null && newDateData.includeTime != includeTime) {
-          newDateData = newDateData.copyWith(includeTime: includeTime);
-        }
-
-        return newDateData;
-      },
+  }) async {
+    // make sure date and time are not updated together from the UI
+    assert(
+      date == null && time == null ||
+          date == null && time != null ||
+          date != null && time == null,
     );
     );
+    String? newTime = time ?? state.time;
 
 
-    return _saveDateData(emit, newDateData);
-  }
-
-  Future<void> _saveDateData(
-    Emitter<DateCellCalendarState> emit,
-    DateCellData newCalData,
-  ) async {
-    if (state.dateCellData == Some(newCalData)) {
-      return;
+    DateTime? newDate = date;
+    if (time != null && time.isNotEmpty) {
+      newDate = state.dateTime ?? DateTime.now();
     }
     }
 
 
-    updateCalData(
-      Option<DateCellData> dateCellData,
-      Option<String> timeFormatError,
-    ) {
-      if (!isClosed) {
-        add(
-          DateCellCalendarEvent.didUpdateCalData(
-            dateCellData,
-            timeFormatError,
-          ),
-        );
-      }
-    }
+    final DateCellData newDateData = DateCellData(
+      dateTime: newDate,
+      time: newTime,
+      includeTime: includeTime ?? state.includeTime,
+    );
 
 
     cellController.saveCellData(
     cellController.saveCellData(
-      newCalData,
+      newDateData,
       onFinish: (result) {
       onFinish: (result) {
         result.fold(
         result.fold(
-          () => updateCalData(Some(newCalData), none()),
+          () {
+            if (!isClosed && state.timeFormatError != null) {
+              add(const DateCellCalendarEvent.didReceiveTimeFormatError(null));
+            }
+          },
           (err) {
           (err) {
             switch (ErrorCode.valueOf(err.code)!) {
             switch (ErrorCode.valueOf(err.code)!) {
               case ErrorCode.InvalidDateTimeFormat:
               case ErrorCode.InvalidDateTimeFormat:
-                updateCalData(state.dateCellData, Some(timeFormatPrompt(err)));
+                if (isClosed) return;
+                add(
+                  DateCellCalendarEvent.didReceiveTimeFormatError(
+                    timeFormatPrompt(err),
+                  ),
+                );
                 break;
                 break;
               default:
               default:
                 Log.error(err);
                 Log.error(err);
@@ -221,25 +193,33 @@ class DateCellCalendarBloc
 
 
 @freezed
 @freezed
 class DateCellCalendarEvent with _$DateCellCalendarEvent {
 class DateCellCalendarEvent with _$DateCellCalendarEvent {
+  // initial event
   const factory DateCellCalendarEvent.initial() = _Initial;
   const factory DateCellCalendarEvent.initial() = _Initial;
-  const factory DateCellCalendarEvent.selectDay(DateTime day) = _SelectDay;
+
+  // notification that cell is updated in the backend
+  const factory DateCellCalendarEvent.didReceiveCellUpdate(
+    DateCellDataPB? data,
+  ) = _DidReceiveCellUpdate;
+  const factory DateCellCalendarEvent.didReceiveTimeFormatError(
+    String? timeformatError,
+  ) = _DidReceiveTimeFormatError;
+
+  // table calendar's UI settings
+  const factory DateCellCalendarEvent.setFocusedDay(DateTime day) = _FocusedDay;
   const factory DateCellCalendarEvent.setCalFormat(CalendarFormat format) =
   const factory DateCellCalendarEvent.setCalFormat(CalendarFormat format) =
       _CalendarFormat;
       _CalendarFormat;
-  const factory DateCellCalendarEvent.setFocusedDay(DateTime day) = _FocusedDay;
+
+  // date cell data is modified
+  const factory DateCellCalendarEvent.selectDay(DateTime day) = _SelectDay;
+  const factory DateCellCalendarEvent.setTime(String time) = _Time;
+  const factory DateCellCalendarEvent.setIncludeTime(bool includeTime) =
+      _IncludeTime;
+
+  // date field type options are modified
   const factory DateCellCalendarEvent.setTimeFormat(TimeFormatPB timeFormat) =
   const factory DateCellCalendarEvent.setTimeFormat(TimeFormatPB timeFormat) =
       _TimeFormat;
       _TimeFormat;
   const factory DateCellCalendarEvent.setDateFormat(DateFormatPB dateFormat) =
   const factory DateCellCalendarEvent.setDateFormat(DateFormatPB dateFormat) =
       _DateFormat;
       _DateFormat;
-  const factory DateCellCalendarEvent.setIncludeTime(bool includeTime) =
-      _IncludeTime;
-  const factory DateCellCalendarEvent.setTime(String time) = _Time;
-  const factory DateCellCalendarEvent.didReceiveCellUpdate(
-    DateCellDataPB? data,
-  ) = _DidReceiveCellUpdate;
-  const factory DateCellCalendarEvent.didUpdateCalData(
-    Option<DateCellData> data,
-    Option<String> timeFormatError,
-  ) = _DidUpdateCalData;
 }
 }
 
 
 @freezed
 @freezed
@@ -248,9 +228,10 @@ class DateCellCalendarState with _$DateCellCalendarState {
     required DateTypeOptionPB dateTypeOptionPB,
     required DateTypeOptionPB dateTypeOptionPB,
     required CalendarFormat format,
     required CalendarFormat format,
     required DateTime focusedDay,
     required DateTime focusedDay,
-    required Option<String> timeFormatError,
-    required Option<DateCellData> dateCellData,
+    required DateTime? dateTime,
     required String? time,
     required String? time,
+    required bool includeTime,
+    required String? timeFormatError,
     required String timeHintText,
     required String timeHintText,
   }) = _DateCellCalendarState;
   }) = _DateCellCalendarState;
 
 
@@ -258,16 +239,15 @@ class DateCellCalendarState with _$DateCellCalendarState {
     DateTypeOptionPB dateTypeOptionPB,
     DateTypeOptionPB dateTypeOptionPB,
     DateCellDataPB? cellData,
     DateCellDataPB? cellData,
   ) {
   ) {
-    Option<DateCellData> dateCellData = calDataFromCellData(cellData);
-    final time =
-        dateCellData.foldRight("", (dateData, previous) => dateData.time ?? '');
+    final dateData = _dateDataFromCellData(cellData);
     return DateCellCalendarState(
     return DateCellCalendarState(
       dateTypeOptionPB: dateTypeOptionPB,
       dateTypeOptionPB: dateTypeOptionPB,
       format: CalendarFormat.month,
       format: CalendarFormat.month,
       focusedDay: DateTime.now(),
       focusedDay: DateTime.now(),
-      time: time,
-      dateCellData: dateCellData,
-      timeFormatError: none(),
+      dateTime: dateData.dateTime,
+      time: dateData.time,
+      includeTime: dateData.includeTime,
+      timeFormatError: null,
       timeHintText: _timeHintText(dateTypeOptionPB),
       timeHintText: _timeHintText(dateTypeOptionPB),
     );
     );
   }
   }
@@ -284,27 +264,21 @@ String _timeHintText(DateTypeOptionPB typeOption) {
   }
   }
 }
 }
 
 
-Option<DateCellData> calDataFromCellData(DateCellDataPB? cellData) {
-  String? time = timeFromCellData(cellData);
-  Option<DateCellData> dateData = none();
-  if (cellData != null) {
-    final timestamp = cellData.timestamp * 1000;
-    final date = DateTime.fromMillisecondsSinceEpoch(timestamp.toInt());
-    dateData = Some(
-      DateCellData(
-        date: date,
-        time: time,
-        includeTime: cellData.includeTime,
-      ),
-    );
+DateCellData _dateDataFromCellData(DateCellDataPB? cellData) {
+  // a null DateCellDataPB may be returned, indicating that all the fields are
+  // at their default values: empty strings and false booleans
+  if (cellData == null) {
+    return const DateCellData(includeTime: false);
   }
   }
-  return dateData;
-}
 
 
-String? timeFromCellData(DateCellDataPB? cellData) {
-  if (cellData == null || !cellData.hasTime()) {
-    return null;
+  DateTime? dateTime;
+  String? time;
+  if (cellData.hasTimestamp()) {
+    final timestamp = cellData.timestamp * 1000;
+    dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp.toInt());
+    time = cellData.time;
   }
   }
+  bool includeTime = cellData.includeTime;
 
 
-  return cellData.time;
+  return DateCellData(dateTime: dateTime, time: time, includeTime: includeTime);
 }
 }

+ 5 - 1
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell_bloc.dart

@@ -79,7 +79,11 @@ class DateCellState with _$DateCellState {
 String _dateStrFromCellData(DateCellDataPB? cellData) {
 String _dateStrFromCellData(DateCellDataPB? cellData) {
   String dateStr = "";
   String dateStr = "";
   if (cellData != null) {
   if (cellData != null) {
-    dateStr = "${cellData.date} ${cellData.time}";
+    if (cellData.includeTime) {
+      dateStr = "${cellData.date} ${cellData.time}";
+    } else {
+      dateStr = cellData.date;
+    }
   }
   }
   return dateStr;
   return dateStr;
 }
 }

+ 42 - 74
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart

@@ -10,8 +10,8 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/theme_extension.dart';
 import 'package:flowy_infra/theme_extension.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/size.dart';
 import 'package:flowy_infra/size.dart';
+import 'package:flowy_infra/time/duration.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
-import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
 import 'package:appflowy_backend/log.dart';
 import 'package:appflowy_backend/log.dart';
 import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
 import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
@@ -89,41 +89,35 @@ class _CellCalendarWidget extends StatefulWidget {
 
 
 class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
 class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
   late PopoverMutex popoverMutex;
   late PopoverMutex popoverMutex;
-  late DateCellCalendarBloc bloc;
 
 
   @override
   @override
   void initState() {
   void initState() {
     popoverMutex = PopoverMutex();
     popoverMutex = PopoverMutex();
 
 
-    bloc = DateCellCalendarBloc(
-      dateTypeOptionPB: widget.dateTypeOptionPB,
-      cellData: widget.cellContext.getCellData(),
-      cellController: widget.cellContext,
-    )..add(const DateCellCalendarEvent.initial());
     super.initState();
     super.initState();
   }
   }
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    return BlocProvider.value(
-      value: bloc,
+    return BlocProvider(
+      create: (context) => DateCellCalendarBloc(
+        dateTypeOptionPB: widget.dateTypeOptionPB,
+        cellData: widget.cellContext.getCellData(),
+        cellController: widget.cellContext,
+      )..add(const DateCellCalendarEvent.initial()),
       child: BlocBuilder<DateCellCalendarBloc, DateCellCalendarState>(
       child: BlocBuilder<DateCellCalendarBloc, DateCellCalendarState>(
-        buildWhen: (p, c) => p != c,
         builder: (context, state) {
         builder: (context, state) {
-          bool includeTime = state.dateCellData
-              .fold(() => false, (dateData) => dateData.includeTime);
           List<Widget> children = [
           List<Widget> children = [
             Padding(
             Padding(
               padding: const EdgeInsets.symmetric(horizontal: 12.0),
               padding: const EdgeInsets.symmetric(horizontal: 12.0),
               child: _buildCalendar(context),
               child: _buildCalendar(context),
             ),
             ),
-            if (includeTime) ...[
-              const VSpace(12.0),
-              _TimeTextField(
-                bloc: context.read<DateCellCalendarBloc>(),
-                popoverMutex: popoverMutex,
-              ),
-            ],
+            AnimatedSwitcher(
+              duration: const Duration(milliseconds: 300),
+              child: state.includeTime
+                  ? _TimeTextField(popoverMutex: popoverMutex)
+                  : const SizedBox(),
+            ),
             const TypeOptionSeparator(spacing: 12.0),
             const TypeOptionSeparator(spacing: 12.0),
             const _IncludeTimeButton(),
             const _IncludeTimeButton(),
             const TypeOptionSeparator(spacing: 12.0),
             const TypeOptionSeparator(spacing: 12.0),
@@ -144,7 +138,6 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
 
 
   @override
   @override
   void dispose() {
   void dispose() {
-    bloc.close();
     popoverMutex.dispose();
     popoverMutex.dispose();
     super.dispose();
     super.dispose();
   }
   }
@@ -208,16 +201,11 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
             outsideTextStyle:
             outsideTextStyle:
                 textStyle.textColor(Theme.of(context).disabledColor),
                 textStyle.textColor(Theme.of(context).disabledColor),
           ),
           ),
-          selectedDayPredicate: (day) {
-            return state.dateCellData.fold(
-              () => false,
-              (dateData) => isSameDay(dateData.date, day),
-            );
-          },
+          selectedDayPredicate: (day) => isSameDay(state.dateTime, day),
           onDaySelected: (selectedDay, focusedDay) {
           onDaySelected: (selectedDay, focusedDay) {
-            context
-                .read<DateCellCalendarBloc>()
-                .add(DateCellCalendarEvent.selectDay(selectedDay));
+            context.read<DateCellCalendarBloc>().add(
+                  DateCellCalendarEvent.selectDay(selectedDay.toLocal().date),
+                );
           },
           },
           onFormatChanged: (format) {
           onFormatChanged: (format) {
             context
             context
@@ -241,10 +229,7 @@ class _IncludeTimeButton extends StatelessWidget {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return BlocSelector<DateCellCalendarBloc, DateCellCalendarState, bool>(
     return BlocSelector<DateCellCalendarBloc, DateCellCalendarState, bool>(
-      selector: (state) => state.dateCellData.fold(
-        () => false,
-        (dateData) => dateData.includeTime,
-      ),
+      selector: (state) => state.includeTime,
       builder: (context, includeTime) {
       builder: (context, includeTime) {
         return Padding(
         return Padding(
           padding: const EdgeInsets.symmetric(horizontal: 12.0),
           padding: const EdgeInsets.symmetric(horizontal: 12.0),
@@ -258,7 +243,7 @@ class _IncludeTimeButton extends StatelessWidget {
                     "grid/clock",
                     "grid/clock",
                     color: Theme.of(context).iconTheme.color,
                     color: Theme.of(context).iconTheme.color,
                   ),
                   ),
-                  const HSpace(4),
+                  const HSpace(6),
                   FlowyText.medium(LocaleKeys.grid_field_includeTime.tr()),
                   FlowyText.medium(LocaleKeys.grid_field_includeTime.tr()),
                   const Spacer(),
                   const Spacer(),
                   Toggle(
                   Toggle(
@@ -280,11 +265,9 @@ class _IncludeTimeButton extends StatelessWidget {
 }
 }
 
 
 class _TimeTextField extends StatefulWidget {
 class _TimeTextField extends StatefulWidget {
-  final DateCellCalendarBloc bloc;
   final PopoverMutex popoverMutex;
   final PopoverMutex popoverMutex;
 
 
   const _TimeTextField({
   const _TimeTextField({
-    required this.bloc,
     required this.popoverMutex,
     required this.popoverMutex,
     Key? key,
     Key? key,
   }) : super(key: key);
   }) : super(key: key);
@@ -295,18 +278,10 @@ class _TimeTextField extends StatefulWidget {
 
 
 class _TimeTextFieldState extends State<_TimeTextField> {
 class _TimeTextFieldState extends State<_TimeTextField> {
   late final FocusNode _focusNode;
   late final FocusNode _focusNode;
-  late final TextEditingController _controller;
 
 
   @override
   @override
   void initState() {
   void initState() {
     _focusNode = FocusNode();
     _focusNode = FocusNode();
-    _controller = TextEditingController(text: widget.bloc.state.time);
-
-    _focusNode.addListener(() {
-      if (mounted) {
-        widget.bloc.add(DateCellCalendarEvent.setTime(_controller.text));
-      }
-    });
 
 
     _focusNode.addListener(() {
     _focusNode.addListener(() {
       if (_focusNode.hasFocus) {
       if (_focusNode.hasFocus) {
@@ -325,38 +300,31 @@ class _TimeTextFieldState extends State<_TimeTextField> {
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    _controller.text = widget.bloc.state.time ?? "";
-    _controller.selection =
-        TextSelection.collapsed(offset: _controller.text.length);
-    return Padding(
-      padding: const EdgeInsets.symmetric(horizontal: 12.0),
-      child: Padding(
-        padding: GridSize.typeOptionContentInsets,
-        child: RoundedInputField(
-          height: GridSize.popoverItemHeight,
-          focusNode: _focusNode,
-          autoFocus: true,
-          hintText: widget.bloc.state.timeHintText,
-          controller: _controller,
-          style: Theme.of(context).textTheme.bodyMedium!,
-          normalBorderColor: Theme.of(context).colorScheme.outline,
-          errorBorderColor: Theme.of(context).colorScheme.error,
-          focusBorderColor: Theme.of(context).colorScheme.primary,
-          cursorColor: Theme.of(context).colorScheme.primary,
-          errorText: widget.bloc.state.timeFormatError
-              .fold(() => "", (error) => error),
-          onEditingComplete: (value) =>
-              widget.bloc.add(DateCellCalendarEvent.setTime(value)),
-        ),
-      ),
+    return BlocBuilder<DateCellCalendarBloc, DateCellCalendarState>(
+      builder: (context, state) {
+        return Column(
+          children: [
+            const VSpace(12),
+            Padding(
+              padding: const EdgeInsets.symmetric(horizontal: 12.0),
+              child: FlowyTextField(
+                text: state.time ?? "",
+                focusNode: _focusNode,
+                submitOnLeave: true,
+                hintText: state.timeHintText,
+                errorText: state.timeFormatError,
+                onSubmitted: (timeString) {
+                  context
+                      .read<DateCellCalendarBloc>()
+                      .add(DateCellCalendarEvent.setTime(timeString));
+                },
+              ),
+            ),
+          ],
+        );
+      },
     );
     );
   }
   }
-
-  @override
-  void dispose() {
-    _focusNode.dispose();
-    super.dispose();
-  }
 }
 }
 
 
 class _DateTypeOptionButton extends StatelessWidget {
 class _DateTypeOptionButton extends StatelessWidget {

+ 2 - 4
frontend/appflowy_flutter/lib/plugins/trash/src/trash_cell.dart

@@ -58,10 +58,8 @@ class TrashCell extends StatelessWidget {
 
 
   String dateFormatter($fixnum.Int64 inputTimestamps) {
   String dateFormatter($fixnum.Int64 inputTimestamps) {
     final outputFormat = DateFormat('MM/dd/yyyy hh:mm a');
     final outputFormat = DateFormat('MM/dd/yyyy hh:mm a');
-    final date = DateTime.fromMillisecondsSinceEpoch(
-      inputTimestamps.toInt() * 1000,
-      isUtc: true,
-    );
+    final date =
+        DateTime.fromMillisecondsSinceEpoch(inputTimestamps.toInt() * 1000);
     final outputDate = outputFormat.format(date);
     final outputDate = outputFormat.format(date);
     return outputDate;
     return outputDate;
   }
   }

+ 64 - 3
frontend/rust-lib/Cargo.lock

@@ -469,10 +469,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552"
 checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552"
 dependencies = [
 dependencies = [
  "chrono",
  "chrono",
- "chrono-tz-build",
+ "chrono-tz-build 0.0.2",
  "phf 0.10.1",
  "phf 0.10.1",
 ]
 ]
 
 
+[[package]]
+name = "chrono-tz"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf9cc2b23599e6d7479755f3594285efb3f74a1bdca7a7374948bc831e23a552"
+dependencies = [
+ "chrono",
+ "chrono-tz-build 0.1.0",
+ "phf 0.11.1",
+]
+
 [[package]]
 [[package]]
 name = "chrono-tz-build"
 name = "chrono-tz-build"
 version = "0.0.2"
 version = "0.0.2"
@@ -481,7 +492,18 @@ checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069"
 dependencies = [
 dependencies = [
  "parse-zoneinfo",
  "parse-zoneinfo",
  "phf 0.10.1",
  "phf 0.10.1",
- "phf_codegen",
+ "phf_codegen 0.10.0",
+]
+
+[[package]]
+name = "chrono-tz-build"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9998fb9f7e9b2111641485bf8beb32f92945f97f92a3d061f744cfef335f751"
+dependencies = [
+ "parse-zoneinfo",
+ "phf 0.11.1",
+ "phf_codegen 0.11.1",
 ]
 ]
 
 
 [[package]]
 [[package]]
@@ -1377,6 +1399,7 @@ dependencies = [
  "async-stream",
  "async-stream",
  "bytes",
  "bytes",
  "chrono",
  "chrono",
+ "chrono-tz 0.8.2",
  "collab",
  "collab",
  "collab-database",
  "collab-database",
  "collab-persistence",
  "collab-persistence",
@@ -3018,6 +3041,15 @@ dependencies = [
  "phf_shared 0.10.0",
  "phf_shared 0.10.0",
 ]
 ]
 
 
+[[package]]
+name = "phf"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c"
+dependencies = [
+ "phf_shared 0.11.1",
+]
+
 [[package]]
 [[package]]
 name = "phf_codegen"
 name = "phf_codegen"
 version = "0.10.0"
 version = "0.10.0"
@@ -3028,6 +3060,16 @@ dependencies = [
  "phf_shared 0.10.0",
  "phf_shared 0.10.0",
 ]
 ]
 
 
+[[package]]
+name = "phf_codegen"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770"
+dependencies = [
+ "phf_generator 0.11.1",
+ "phf_shared 0.11.1",
+]
+
 [[package]]
 [[package]]
 name = "phf_generator"
 name = "phf_generator"
 version = "0.8.0"
 version = "0.8.0"
@@ -3048,6 +3090,16 @@ dependencies = [
  "rand 0.8.5",
  "rand 0.8.5",
 ]
 ]
 
 
+[[package]]
+name = "phf_generator"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf"
+dependencies = [
+ "phf_shared 0.11.1",
+ "rand 0.8.5",
+]
+
 [[package]]
 [[package]]
 name = "phf_macros"
 name = "phf_macros"
 version = "0.8.0"
 version = "0.8.0"
@@ -3081,6 +3133,15 @@ dependencies = [
  "uncased",
  "uncased",
 ]
 ]
 
 
+[[package]]
+name = "phf_shared"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676"
+dependencies = [
+ "siphasher",
+]
+
 [[package]]
 [[package]]
 name = "pin-project"
 name = "pin-project"
 version = "1.0.12"
 version = "1.0.12"
@@ -4090,7 +4151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "95a665751302f22a03c56721e23094e4dc22b04a80f381e6737a07bf7a7c70c0"
 checksum = "95a665751302f22a03c56721e23094e4dc22b04a80f381e6737a07bf7a7c70c0"
 dependencies = [
 dependencies = [
  "chrono",
  "chrono",
- "chrono-tz",
+ "chrono-tz 0.6.1",
  "globwalk",
  "globwalk",
  "humansize",
  "humansize",
  "lazy_static",
  "lazy_static",

+ 1 - 0
frontend/rust-lib/flowy-database2/Cargo.toml

@@ -39,6 +39,7 @@ anyhow = "1.0"
 async-stream = "0.3.4"
 async-stream = "0.3.4"
 rayon = "1.6.1"
 rayon = "1.6.1"
 nanoid = "0.4.0"
 nanoid = "0.4.0"
+chrono-tz = "0.8.1"
 
 
 strum = "0.21"
 strum = "0.21"
 strum_macros = "0.21"
 strum_macros = "0.21"

+ 5 - 7
frontend/rust-lib/flowy-database2/src/entities/type_option_entities/date_entities.rs

@@ -20,6 +20,9 @@ pub struct DateCellDataPB {
 
 
   #[pb(index = 4)]
   #[pb(index = 4)]
   pub include_time: bool,
   pub include_time: bool,
+
+  #[pb(index = 5)]
+  pub timezone_id: String,
 }
 }
 
 
 #[derive(Clone, Debug, Default, ProtoBuf)]
 #[derive(Clone, Debug, Default, ProtoBuf)]
@@ -36,8 +39,8 @@ pub struct DateChangesetPB {
   #[pb(index = 4, one_of)]
   #[pb(index = 4, one_of)]
   pub include_time: Option<bool>,
   pub include_time: Option<bool>,
 
 
-  #[pb(index = 5)]
-  pub is_utc: bool,
+  #[pb(index = 5, one_of)]
+  pub timezone_id: Option<String>,
 }
 }
 
 
 // Date
 // Date
@@ -48,9 +51,6 @@ pub struct DateTypeOptionPB {
 
 
   #[pb(index = 2)]
   #[pb(index = 2)]
   pub time_format: TimeFormatPB,
   pub time_format: TimeFormatPB,
-
-  #[pb(index = 3)]
-  pub include_time: bool,
 }
 }
 
 
 impl From<DateTypeOption> for DateTypeOptionPB {
 impl From<DateTypeOption> for DateTypeOptionPB {
@@ -58,7 +58,6 @@ impl From<DateTypeOption> for DateTypeOptionPB {
     Self {
     Self {
       date_format: data.date_format.into(),
       date_format: data.date_format.into(),
       time_format: data.time_format.into(),
       time_format: data.time_format.into(),
-      include_time: data.include_time,
     }
     }
   }
   }
 }
 }
@@ -68,7 +67,6 @@ impl From<DateTypeOptionPB> for DateTypeOption {
     Self {
     Self {
       date_format: data.date_format.into(),
       date_format: data.date_format.into(),
       time_format: data.time_format.into(),
       time_format: data.time_format.into(),
-      include_time: data.include_time,
     }
     }
   }
   }
 }
 }

+ 6 - 6
frontend/rust-lib/flowy-database2/src/event_handler.rs

@@ -361,7 +361,7 @@ pub(crate) async fn update_cell_handler(
       &params.field_id,
       &params.field_id,
       params.cell_changeset.clone(),
       params.cell_changeset.clone(),
     )
     )
-    .await;
+    .await?;
   Ok(())
   Ok(())
 }
 }
 
 
@@ -397,7 +397,7 @@ pub(crate) async fn insert_or_update_select_option_handler(
       RowId::from(params.row_id),
       RowId::from(params.row_id),
       params.items,
       params.items,
     )
     )
-    .await;
+    .await?;
   Ok(())
   Ok(())
 }
 }
 
 
@@ -415,7 +415,7 @@ pub(crate) async fn delete_select_option_handler(
       RowId::from(params.row_id),
       RowId::from(params.row_id),
       params.items,
       params.items,
     )
     )
-    .await;
+    .await?;
   Ok(())
   Ok(())
 }
 }
 
 
@@ -452,7 +452,7 @@ pub(crate) async fn update_select_option_cell_handler(
       &params.cell_identifier.field_id,
       &params.cell_identifier.field_id,
       changeset,
       changeset,
     )
     )
-    .await;
+    .await?;
   Ok(())
   Ok(())
 }
 }
 
 
@@ -467,7 +467,7 @@ pub(crate) async fn update_date_cell_handler(
     date: data.date,
     date: data.date,
     time: data.time,
     time: data.time,
     include_time: data.include_time,
     include_time: data.include_time,
-    is_utc: data.is_utc,
+    timezone_id: data.timezone_id,
   };
   };
   let database_editor = manager.get_database(&cell_id.view_id).await?;
   let database_editor = manager.get_database(&cell_id.view_id).await?;
   database_editor
   database_editor
@@ -477,7 +477,7 @@ pub(crate) async fn update_date_cell_handler(
       &cell_id.field_id,
       &cell_id.field_id,
       cell_changeset,
       cell_changeset,
     )
     )
-    .await;
+    .await?;
   Ok(())
   Ok(())
 }
 }
 
 

+ 12 - 14
frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs

@@ -4,7 +4,7 @@ use std::fmt::Debug;
 use collab_database::fields::Field;
 use collab_database::fields::Field;
 use collab_database::rows::{get_field_type_from_cell, Cell, Cells};
 use collab_database::rows::{get_field_type_from_cell, Cell, Cells};
 
 
-use flowy_error::{ErrorCode, FlowyResult};
+use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 
 
 use crate::entities::FieldType;
 use crate::entities::FieldType;
 use crate::services::cell::{CellCache, CellProtobufBlob};
 use crate::services::cell::{CellCache, CellProtobufBlob};
@@ -63,16 +63,14 @@ pub fn apply_cell_data_changeset<C: ToCellChangeset>(
   cell: Option<Cell>,
   cell: Option<Cell>,
   field: &Field,
   field: &Field,
   cell_data_cache: Option<CellCache>,
   cell_data_cache: Option<CellCache>,
-) -> Cell {
+) -> Result<Cell, FlowyError> {
   let changeset = changeset.to_cell_changeset_str();
   let changeset = changeset.to_cell_changeset_str();
   let field_type = FieldType::from(field.field_type);
   let field_type = FieldType::from(field.field_type);
   match TypeOptionCellExt::new_with_cell_data_cache(field, cell_data_cache)
   match TypeOptionCellExt::new_with_cell_data_cache(field, cell_data_cache)
     .get_type_option_cell_data_handler(&field_type)
     .get_type_option_cell_data_handler(&field_type)
   {
   {
-    None => Cell::default(),
-    Some(handler) => handler
-      .handle_cell_changeset(changeset, cell, field)
-      .unwrap_or_default(),
+    None => Ok(Cell::default()),
+    Some(handler) => Ok(handler.handle_cell_changeset(changeset, cell, field)?),
   }
   }
 }
 }
 
 
@@ -196,11 +194,11 @@ pub fn stringify_cell_data(
 }
 }
 
 
 pub fn insert_text_cell(s: String, field: &Field) -> Cell {
 pub fn insert_text_cell(s: String, field: &Field) -> Cell {
-  apply_cell_data_changeset(s, None, field, None)
+  apply_cell_data_changeset(s, None, field, None).unwrap()
 }
 }
 
 
 pub fn insert_number_cell(num: i64, field: &Field) -> Cell {
 pub fn insert_number_cell(num: i64, field: &Field) -> Cell {
-  apply_cell_data_changeset(num.to_string(), None, field, None)
+  apply_cell_data_changeset(num.to_string(), None, field, None).unwrap()
 }
 }
 
 
 pub fn insert_url_cell(url: String, field: &Field) -> Cell {
 pub fn insert_url_cell(url: String, field: &Field) -> Cell {
@@ -214,7 +212,7 @@ pub fn insert_url_cell(url: String, field: &Field) -> Cell {
     _ => url,
     _ => url,
   };
   };
 
 
-  apply_cell_data_changeset(url, None, field, None)
+  apply_cell_data_changeset(url, None, field, None).unwrap()
 }
 }
 
 
 pub fn insert_checkbox_cell(is_check: bool, field: &Field) -> Cell {
 pub fn insert_checkbox_cell(is_check: bool, field: &Field) -> Cell {
@@ -223,7 +221,7 @@ pub fn insert_checkbox_cell(is_check: bool, field: &Field) -> Cell {
   } else {
   } else {
     UNCHECK.to_string()
     UNCHECK.to_string()
   };
   };
-  apply_cell_data_changeset(s, None, field, None)
+  apply_cell_data_changeset(s, None, field, None).unwrap()
 }
 }
 
 
 pub fn insert_date_cell(timestamp: i64, field: &Field) -> Cell {
 pub fn insert_date_cell(timestamp: i64, field: &Field) -> Cell {
@@ -231,22 +229,22 @@ pub fn insert_date_cell(timestamp: i64, field: &Field) -> Cell {
     date: Some(timestamp.to_string()),
     date: Some(timestamp.to_string()),
     time: None,
     time: None,
     include_time: Some(false),
     include_time: Some(false),
-    is_utc: true,
+    timezone_id: None,
   })
   })
   .unwrap();
   .unwrap();
-  apply_cell_data_changeset(cell_data, None, field, None)
+  apply_cell_data_changeset(cell_data, None, field, None).unwrap()
 }
 }
 
 
 pub fn insert_select_option_cell(option_ids: Vec<String>, field: &Field) -> Cell {
 pub fn insert_select_option_cell(option_ids: Vec<String>, field: &Field) -> Cell {
   let changeset =
   let changeset =
     SelectOptionCellChangeset::from_insert_options(option_ids).to_cell_changeset_str();
     SelectOptionCellChangeset::from_insert_options(option_ids).to_cell_changeset_str();
-  apply_cell_data_changeset(changeset, None, field, None)
+  apply_cell_data_changeset(changeset, None, field, None).unwrap()
 }
 }
 
 
 pub fn delete_select_option_cell(option_ids: Vec<String>, field: &Field) -> Cell {
 pub fn delete_select_option_cell(option_ids: Vec<String>, field: &Field) -> Cell {
   let changeset =
   let changeset =
     SelectOptionCellChangeset::from_delete_options(option_ids).to_cell_changeset_str();
     SelectOptionCellChangeset::from_delete_options(option_ids).to_cell_changeset_str();
-  apply_cell_data_changeset(changeset, None, field, None)
+  apply_cell_data_changeset(changeset, None, field, None).unwrap()
 }
 }
 
 
 /// Deserialize the String into cell specific data type.
 /// Deserialize the String into cell specific data type.

+ 34 - 15
frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs

@@ -466,20 +466,27 @@ impl DatabaseEditor {
     row_id: RowId,
     row_id: RowId,
     field_id: &str,
     field_id: &str,
     cell_changeset: T,
     cell_changeset: T,
-  ) -> Option<()>
+  ) -> FlowyResult<()>
   where
   where
     T: ToCellChangeset,
     T: ToCellChangeset,
   {
   {
     let (field, cell) = {
     let (field, cell) = {
       let database = self.database.lock();
       let database = self.database.lock();
+      let field = match database.fields.get_field(field_id) {
+        Some(field) => Ok(field),
+        None => {
+          let msg = format!("Field with id:{} not found", &field_id);
+          Err(FlowyError::internal().context(msg))
+        },
+      }?;
       (
       (
-        database.fields.get_field(field_id)?,
+        field,
         database.get_cell(field_id, &row_id).map(|cell| cell.cell),
         database.get_cell(field_id, &row_id).map(|cell| cell.cell),
       )
       )
     };
     };
     let cell_changeset = cell_changeset.to_cell_changeset_str();
     let cell_changeset = cell_changeset.to_cell_changeset_str();
     let new_cell =
     let new_cell =
-      apply_cell_data_changeset(cell_changeset, cell, &field, Some(self.cell_cache.clone()));
+      apply_cell_data_changeset(cell_changeset, cell, &field, Some(self.cell_cache.clone()))?;
     self.update_cell(view_id, row_id, field_id, new_cell).await
     self.update_cell(view_id, row_id, field_id, new_cell).await
   }
   }
 
 
@@ -489,7 +496,7 @@ impl DatabaseEditor {
     row_id: RowId,
     row_id: RowId,
     field_id: &str,
     field_id: &str,
     new_cell: Cell,
     new_cell: Cell,
-  ) -> Option<()> {
+  ) -> FlowyResult<()> {
     let old_row = { self.database.lock().get_row(&row_id) };
     let old_row = { self.database.lock().get_row(&row_id) };
     self.database.lock().update_row(&row_id, |row_update| {
     self.database.lock().update_row(&row_id, |row_update| {
       row_update.update_cells(|cell_update| {
       row_update.update_cells(|cell_update| {
@@ -516,7 +523,7 @@ impl DatabaseEditor {
       field_id: field_id.to_string(),
       field_id: field_id.to_string(),
     }])
     }])
     .await;
     .await;
-    None
+    Ok(())
   }
   }
 
 
   pub async fn create_select_option(
   pub async fn create_select_option(
@@ -536,9 +543,15 @@ impl DatabaseEditor {
     field_id: &str,
     field_id: &str,
     row_id: RowId,
     row_id: RowId,
     options: Vec<SelectOptionPB>,
     options: Vec<SelectOptionPB>,
-  ) -> Option<()> {
-    let field = self.database.lock().fields.get_field(field_id)?;
-    let mut type_option = select_type_option_from_field(&field).ok()?;
+  ) -> FlowyResult<()> {
+    let field = match self.database.lock().fields.get_field(field_id) {
+      Some(field) => Ok(field),
+      None => {
+        let msg = format!("Field with id:{} not found", &field_id);
+        Err(FlowyError::internal().context(msg))
+      },
+    }?;
+    let mut type_option = select_type_option_from_field(&field)?;
     let cell_changeset = SelectOptionCellChangeset {
     let cell_changeset = SelectOptionCellChangeset {
       insert_option_ids: options.iter().map(|option| option.id.clone()).collect(),
       insert_option_ids: options.iter().map(|option| option.id.clone()).collect(),
       ..Default::default()
       ..Default::default()
@@ -557,8 +570,8 @@ impl DatabaseEditor {
 
 
     self
     self
       .update_cell_with_changeset(view_id, row_id, field_id, cell_changeset)
       .update_cell_with_changeset(view_id, row_id, field_id, cell_changeset)
-      .await;
-    None
+      .await?;
+    Ok(())
   }
   }
 
 
   pub async fn delete_select_options(
   pub async fn delete_select_options(
@@ -567,9 +580,15 @@ impl DatabaseEditor {
     field_id: &str,
     field_id: &str,
     row_id: RowId,
     row_id: RowId,
     options: Vec<SelectOptionPB>,
     options: Vec<SelectOptionPB>,
-  ) -> Option<()> {
-    let field = self.database.lock().fields.get_field(field_id)?;
-    let mut type_option = select_type_option_from_field(&field).ok()?;
+  ) -> FlowyResult<()> {
+    let field = match self.database.lock().fields.get_field(field_id) {
+      Some(field) => Ok(field),
+      None => {
+        let msg = format!("Field with id:{} not found", &field_id);
+        Err(FlowyError::internal().context(msg))
+      },
+    }?;
+    let mut type_option = select_type_option_from_field(&field)?;
     let cell_changeset = SelectOptionCellChangeset {
     let cell_changeset = SelectOptionCellChangeset {
       delete_option_ids: options.iter().map(|option| option.id.clone()).collect(),
       delete_option_ids: options.iter().map(|option| option.id.clone()).collect(),
       ..Default::default()
       ..Default::default()
@@ -588,8 +607,8 @@ impl DatabaseEditor {
 
 
     self
     self
       .update_cell_with_changeset(view_id, row_id, field_id, cell_changeset)
       .update_cell_with_changeset(view_id, row_id, field_id, cell_changeset)
-      .await;
-    None
+      .await?;
+    Ok(())
   }
   }
 
 
   pub async fn get_select_options(&self, row_id: RowId, field_id: &str) -> SelectOptionCellDataPB {
   pub async fn get_select_options(&self, row_id: RowId, field_id: &str) -> SelectOptionCellDataPB {

+ 341 - 82
frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_tests.rs

@@ -20,19 +20,74 @@ mod tests {
       type_option.date_format = date_format;
       type_option.date_format = date_format;
       match date_format {
       match date_format {
         DateFormat::Friendly => {
         DateFormat::Friendly => {
-          assert_date(&type_option, 1647251762, None, "Mar 14,2022", false, &field);
+          assert_date(
+            &type_option,
+            &field,
+            DateCellChangeset {
+              date: Some("1647251762".to_owned()),
+              time: None,
+              include_time: None,
+              timezone_id: None,
+            },
+            None,
+            "Mar 14, 2022",
+          );
         },
         },
         DateFormat::US => {
         DateFormat::US => {
-          assert_date(&type_option, 1647251762, None, "2022/03/14", false, &field);
+          assert_date(
+            &type_option,
+            &field,
+            DateCellChangeset {
+              date: Some("1647251762".to_owned()),
+              time: None,
+              include_time: None,
+              timezone_id: None,
+            },
+            None,
+            "2022/03/14",
+          );
         },
         },
         DateFormat::ISO => {
         DateFormat::ISO => {
-          assert_date(&type_option, 1647251762, None, "2022-03-14", false, &field);
+          assert_date(
+            &type_option,
+            &field,
+            DateCellChangeset {
+              date: Some("1647251762".to_owned()),
+              time: None,
+              include_time: None,
+              timezone_id: None,
+            },
+            None,
+            "2022-03-14",
+          );
         },
         },
         DateFormat::Local => {
         DateFormat::Local => {
-          assert_date(&type_option, 1647251762, None, "03/14/2022", false, &field);
+          assert_date(
+            &type_option,
+            &field,
+            DateCellChangeset {
+              date: Some("1647251762".to_owned()),
+              time: None,
+              include_time: None,
+              timezone_id: None,
+            },
+            None,
+            "03/14/2022",
+          );
         },
         },
         DateFormat::DayMonthYear => {
         DateFormat::DayMonthYear => {
-          assert_date(&type_option, 1647251762, None, "14/03/2022", false, &field);
+          assert_date(
+            &type_option,
+            &field,
+            DateCellChangeset {
+              date: Some("1647251762".to_owned()),
+              time: None,
+              include_time: None,
+              timezone_id: None,
+            },
+            None,
+            "14/03/2022",
+          );
         },
         },
       }
       }
     }
     }
@@ -41,8 +96,7 @@ mod tests {
   #[test]
   #[test]
   fn date_type_option_different_time_format_test() {
   fn date_type_option_different_time_format_test() {
     let mut type_option = DateTypeOption::default();
     let mut type_option = DateTypeOption::default();
-    let field_type = FieldType::DateTime;
-    let field_rev = FieldBuilder::from_field_type(field_type).build();
+    let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
 
 
     for time_format in TimeFormat::iter() {
     for time_format in TimeFormat::iter() {
       type_option.time_format = time_format;
       type_option.time_format = time_format;
@@ -50,53 +104,77 @@ mod tests {
         TimeFormat::TwentyFourHour => {
         TimeFormat::TwentyFourHour => {
           assert_date(
           assert_date(
             &type_option,
             &type_option,
-            1653609600,
+            &field,
+            DateCellChangeset {
+              date: Some("1653609600".to_owned()),
+              time: None,
+              include_time: Some(true),
+              timezone_id: Some("Etc/UTC".to_owned()),
+            },
             None,
             None,
-            "May 27,2022 00:00",
-            true,
-            &field_rev,
+            "May 27, 2022 00:00",
           );
           );
           assert_date(
           assert_date(
             &type_option,
             &type_option,
-            1653609600,
-            Some("9:00".to_owned()),
-            "May 27,2022 09:00",
-            true,
-            &field_rev,
+            &field,
+            DateCellChangeset {
+              date: Some("1653609600".to_owned()),
+              time: Some("9:00".to_owned()),
+              include_time: Some(true),
+              timezone_id: Some("Etc/UTC".to_owned()),
+            },
+            None,
+            "May 27, 2022 09:00",
           );
           );
           assert_date(
           assert_date(
             &type_option,
             &type_option,
-            1653609600,
-            Some("23:00".to_owned()),
-            "May 27,2022 23:00",
-            true,
-            &field_rev,
+            &field,
+            DateCellChangeset {
+              date: Some("1653609600".to_owned()),
+              time: Some("23:00".to_owned()),
+              include_time: Some(true),
+              timezone_id: Some("Etc/UTC".to_owned()),
+            },
+            None,
+            "May 27, 2022 23:00",
           );
           );
         },
         },
         TimeFormat::TwelveHour => {
         TimeFormat::TwelveHour => {
           assert_date(
           assert_date(
             &type_option,
             &type_option,
-            1653609600,
+            &field,
+            DateCellChangeset {
+              date: Some("1653609600".to_owned()),
+              time: None,
+              include_time: Some(true),
+              timezone_id: Some("Etc/UTC".to_owned()),
+            },
             None,
             None,
-            "May 27,2022 12:00 AM",
-            true,
-            &field_rev,
+            "May 27, 2022 12:00 AM",
           );
           );
           assert_date(
           assert_date(
             &type_option,
             &type_option,
-            1653609600,
-            Some("9:00 AM".to_owned()),
-            "May 27,2022 09:00 AM",
-            true,
-            &field_rev,
+            &field,
+            DateCellChangeset {
+              date: Some("1653609600".to_owned()),
+              time: Some("9:00 AM".to_owned()),
+              include_time: Some(true),
+              timezone_id: None,
+            },
+            None,
+            "May 27, 2022 09:00 AM",
           );
           );
           assert_date(
           assert_date(
             &type_option,
             &type_option,
-            1653609600,
-            Some("11:23 pm".to_owned()),
-            "May 27,2022 11:23 PM",
-            true,
-            &field_rev,
+            &field,
+            DateCellChangeset {
+              date: Some("1653609600".to_owned()),
+              time: Some("11:23 pm".to_owned()),
+              include_time: Some(true),
+              timezone_id: Some("Etc/UTC".to_owned()),
+            },
+            None,
+            "May 27, 2022 11:23 PM",
           );
           );
         },
         },
       }
       }
@@ -107,38 +185,58 @@ mod tests {
   fn date_type_option_invalid_date_str_test() {
   fn date_type_option_invalid_date_str_test() {
     let type_option = DateTypeOption::default();
     let type_option = DateTypeOption::default();
     let field_type = FieldType::DateTime;
     let field_type = FieldType::DateTime;
-    let field_rev = FieldBuilder::from_field_type(field_type).build();
-    assert_date(&type_option, "abc", None, "", false, &field_rev);
+    let field = FieldBuilder::from_field_type(field_type).build();
+    assert_date(
+      &type_option,
+      &field,
+      DateCellChangeset {
+        date: Some("abc".to_owned()),
+        time: None,
+        include_time: None,
+        timezone_id: None,
+      },
+      None,
+      "",
+    );
   }
   }
 
 
   #[test]
   #[test]
   #[should_panic]
   #[should_panic]
   fn date_type_option_invalid_include_time_str_test() {
   fn date_type_option_invalid_include_time_str_test() {
     let type_option = DateTypeOption::new();
     let type_option = DateTypeOption::new();
-    let field_rev = FieldBuilder::from_field_type(FieldType::DateTime).build();
+    let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
 
 
     assert_date(
     assert_date(
       &type_option,
       &type_option,
-      1653609600,
-      Some("1:".to_owned()),
-      "May 27,2022 01:00",
-      true,
-      &field_rev,
+      &field,
+      DateCellChangeset {
+        date: Some("1653609600".to_owned()),
+        time: Some("1:".to_owned()),
+        include_time: Some(true),
+        timezone_id: None,
+      },
+      None,
+      "May 27, 2022 01:00",
     );
     );
   }
   }
 
 
   #[test]
   #[test]
+  #[should_panic]
   fn date_type_option_empty_include_time_str_test() {
   fn date_type_option_empty_include_time_str_test() {
     let type_option = DateTypeOption::new();
     let type_option = DateTypeOption::new();
-    let field_rev = FieldBuilder::from_field_type(FieldType::DateTime).build();
+    let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
 
 
     assert_date(
     assert_date(
       &type_option,
       &type_option,
-      1653609600,
-      Some("".to_owned()),
-      "May 27,2022 00:00",
-      true,
-      &field_rev,
+      &field,
+      DateCellChangeset {
+        date: Some("1653609600".to_owned()),
+        time: Some("".to_owned()),
+        include_time: Some(true),
+        timezone_id: None,
+      },
+      None,
+      "May 27, 2022 01:00",
     );
     );
   }
   }
 
 
@@ -146,14 +244,18 @@ mod tests {
   fn date_type_midnight_include_time_str_test() {
   fn date_type_midnight_include_time_str_test() {
     let type_option = DateTypeOption::new();
     let type_option = DateTypeOption::new();
     let field_type = FieldType::DateTime;
     let field_type = FieldType::DateTime;
-    let field_rev = FieldBuilder::from_field_type(field_type).build();
+    let field = FieldBuilder::from_field_type(field_type).build();
     assert_date(
     assert_date(
       &type_option,
       &type_option,
-      1653609600,
-      Some("00:00".to_owned()),
-      "May 27,2022 00:00",
-      true,
-      &field_rev,
+      &field,
+      DateCellChangeset {
+        date: Some("1653609600".to_owned()),
+        time: Some("00:00".to_owned()),
+        include_time: Some(true),
+        timezone_id: None,
+      },
+      None,
+      "May 27, 2022 00:00",
     );
     );
   }
   }
 
 
@@ -162,15 +264,18 @@ mod tests {
   #[should_panic]
   #[should_panic]
   fn date_type_option_twelve_hours_include_time_str_in_twenty_four_hours_format() {
   fn date_type_option_twelve_hours_include_time_str_in_twenty_four_hours_format() {
     let type_option = DateTypeOption::new();
     let type_option = DateTypeOption::new();
-    let field_rev = FieldBuilder::from_field_type(FieldType::DateTime).build();
-
+    let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
     assert_date(
     assert_date(
       &type_option,
       &type_option,
-      1653609600,
-      Some("1:00 am".to_owned()),
-      "May 27,2022 01:00 AM",
-      true,
-      &field_rev,
+      &field,
+      DateCellChangeset {
+        date: Some("1653609600".to_owned()),
+        time: Some("1:00 am".to_owned()),
+        include_time: Some(true),
+        timezone_id: None,
+      },
+      None,
+      "May 27, 2022 01:00 AM",
     );
     );
   }
   }
 
 
@@ -180,15 +285,19 @@ mod tests {
   fn date_type_option_twenty_four_hours_include_time_str_in_twelve_hours_format() {
   fn date_type_option_twenty_four_hours_include_time_str_in_twelve_hours_format() {
     let mut type_option = DateTypeOption::new();
     let mut type_option = DateTypeOption::new();
     type_option.time_format = TimeFormat::TwelveHour;
     type_option.time_format = TimeFormat::TwelveHour;
-    let field_rev = FieldBuilder::from_field_type(FieldType::DateTime).build();
+    let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
 
 
     assert_date(
     assert_date(
       &type_option,
       &type_option,
-      1653609600,
-      Some("20:00".to_owned()),
-      "May 27,2022 08:00 PM",
-      true,
-      &field_rev,
+      &field,
+      DateCellChangeset {
+        date: Some("1653609600".to_owned()),
+        time: Some("20:00".to_owned()),
+        include_time: Some(true),
+        timezone_id: None,
+      },
+      None,
+      "May 27, 2022 08:00 PM",
     );
     );
   }
   }
 
 
@@ -218,25 +327,170 @@ mod tests {
     assert_eq!(china_local_time, "03/14/2022 05:56 PM");
     assert_eq!(china_local_time, "03/14/2022 05:56 PM");
   }
   }
 
 
-  fn assert_date<T: ToString>(
+  /// The time component shouldn't remain the same since the timestamp is
+  /// completely overwritten. To achieve the desired result, also pass in the
+  /// time string along with the new timestamp.
+  #[test]
+  #[should_panic]
+  fn update_date_keep_time() {
+    let type_option = DateTypeOption::new();
+    let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
+
+    let old_cell_data = initialize_date_cell(
+      &type_option,
+      DateCellChangeset {
+        date: Some("1700006400".to_owned()),
+        time: Some("08:00".to_owned()),
+        include_time: Some(true),
+        timezone_id: Some("Etc/UTC".to_owned()),
+      },
+    );
+    assert_date(
+      &type_option,
+      &field,
+      DateCellChangeset {
+        date: Some("1701302400".to_owned()),
+        time: None,
+        include_time: None,
+        timezone_id: None,
+      },
+      Some(old_cell_data),
+      "Nov 30, 2023 08:00",
+    );
+  }
+
+  #[test]
+  fn update_time_keep_date() {
+    let type_option = DateTypeOption::new();
+    let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
+
+    let old_cell_data = initialize_date_cell(
+      &type_option,
+      DateCellChangeset {
+        date: Some("1700006400".to_owned()),
+        time: Some("08:00".to_owned()),
+        include_time: Some(true),
+        timezone_id: None,
+      },
+    );
+    assert_date(
+      &type_option,
+      &field,
+      DateCellChangeset {
+        date: None,
+        time: Some("14:00".to_owned()),
+        include_time: None,
+        timezone_id: None,
+      },
+      Some(old_cell_data),
+      "Nov 15, 2023 14:00",
+    );
+  }
+
+  #[test]
+  fn timezone_no_daylight_saving_time() {
+    let type_option = DateTypeOption::new();
+    let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
+
+    assert_date(
+      &type_option,
+      &field,
+      DateCellChangeset {
+        date: Some("1672963200".to_owned()),
+        time: None,
+        include_time: Some(true),
+        timezone_id: Some("Asia/Tokyo".to_owned()),
+      },
+      None,
+      "Jan 06, 2023 09:00",
+    );
+    assert_date(
+      &type_option,
+      &field,
+      DateCellChangeset {
+        date: Some("1685404800".to_owned()),
+        time: None,
+        include_time: Some(true),
+        timezone_id: Some("Asia/Tokyo".to_owned()),
+      },
+      None,
+      "May 30, 2023 09:00",
+    );
+  }
+
+  #[test]
+  fn timezone_with_daylight_saving_time() {
+    let type_option = DateTypeOption::new();
+    let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
+
+    assert_date(
+      &type_option,
+      &field,
+      DateCellChangeset {
+        date: Some("1672963200".to_owned()),
+        time: None,
+        include_time: Some(true),
+        timezone_id: Some("Europe/Paris".to_owned()),
+      },
+      None,
+      "Jan 06, 2023 01:00",
+    );
+    assert_date(
+      &type_option,
+      &field,
+      DateCellChangeset {
+        date: Some("1685404800".to_owned()),
+        time: None,
+        include_time: Some(true),
+        timezone_id: Some("Europe/Paris".to_owned()),
+      },
+      None,
+      "May 30, 2023 02:00",
+    );
+  }
+
+  #[test]
+  fn change_timezone() {
+    let type_option = DateTypeOption::new();
+    let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
+
+    let old_cell_data = initialize_date_cell(
+      &type_option,
+      DateCellChangeset {
+        date: Some("1672963200".to_owned()),
+        time: None,
+        include_time: Some(true),
+        timezone_id: Some("Asia/China".to_owned()),
+      },
+    );
+    assert_date(
+      &type_option,
+      &field,
+      DateCellChangeset {
+        date: None,
+        time: None,
+        include_time: None,
+        timezone_id: Some("America/Los_Angeles".to_owned()),
+      },
+      Some(old_cell_data),
+      "Jan 05, 2023 16:00",
+    );
+  }
+
+  fn assert_date(
     type_option: &DateTypeOption,
     type_option: &DateTypeOption,
-    timestamp: T,
-    include_time_str: Option<String>,
-    expected_str: &str,
-    include_time: bool,
     field: &Field,
     field: &Field,
+    changeset: DateCellChangeset,
+    old_cell_data: Option<Cell>,
+    expected_str: &str,
   ) {
   ) {
-    let changeset = DateCellChangeset {
-      date: Some(timestamp.to_string()),
-      time: include_time_str,
-      is_utc: false,
-      include_time: Some(include_time),
-    };
-    let (cell, _) = type_option.apply_changeset(changeset, None).unwrap();
+    let (cell, cell_data) = type_option
+      .apply_changeset(changeset, old_cell_data)
+      .unwrap();
 
 
     assert_eq!(
     assert_eq!(
-      decode_cell_data(&cell, type_option, include_time, field),
-      expected_str.to_owned(),
+      decode_cell_data(&cell, type_option, cell_data.include_time, field),
+      expected_str,
     );
     );
   }
   }
 
 
@@ -258,4 +512,9 @@ mod tests {
       decoded_data.date
       decoded_data.date
     }
     }
   }
   }
+
+  fn initialize_date_cell(type_option: &DateTypeOption, changeset: DateCellChangeset) -> Cell {
+    let (cell, _) = type_option.apply_changeset(changeset, None).unwrap();
+    cell
+  }
 }
 }

+ 130 - 62
frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs

@@ -5,20 +5,21 @@ use crate::services::field::{
   TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform,
   TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform,
 };
 };
 use chrono::format::strftime::StrftimeItems;
 use chrono::format::strftime::StrftimeItems;
-use chrono::NaiveDateTime;
+use chrono::{DateTime, Local, NaiveDateTime, NaiveTime, Offset, TimeZone};
+use chrono_tz::Tz;
 use collab::core::any_map::AnyMapExtension;
 use collab::core::any_map::AnyMapExtension;
 use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
 use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
 use collab_database::rows::Cell;
 use collab_database::rows::Cell;
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use serde::{Deserialize, Serialize};
 use serde::{Deserialize, Serialize};
 use std::cmp::Ordering;
 use std::cmp::Ordering;
+use std::str::FromStr;
 
 
 // Date
 // Date
 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
 pub struct DateTypeOption {
 pub struct DateTypeOption {
   pub date_format: DateFormat,
   pub date_format: DateFormat,
   pub time_format: TimeFormat,
   pub time_format: TimeFormat,
-  pub include_time: bool,
 }
 }
 
 
 impl TypeOption for DateTypeOption {
 impl TypeOption for DateTypeOption {
@@ -30,7 +31,6 @@ impl TypeOption for DateTypeOption {
 
 
 impl From<TypeOptionData> for DateTypeOption {
 impl From<TypeOptionData> for DateTypeOption {
   fn from(data: TypeOptionData) -> Self {
   fn from(data: TypeOptionData) -> Self {
-    let include_time = data.get_bool_value("include_time").unwrap_or(false);
     let date_format = data
     let date_format = data
       .get_i64_value("data_format")
       .get_i64_value("data_format")
       .map(DateFormat::from)
       .map(DateFormat::from)
@@ -42,7 +42,6 @@ impl From<TypeOptionData> for DateTypeOption {
     Self {
     Self {
       date_format,
       date_format,
       time_format,
       time_format,
-      include_time,
     }
     }
   }
   }
 }
 }
@@ -52,7 +51,6 @@ impl From<DateTypeOption> for TypeOptionData {
     TypeOptionDataBuilder::new()
     TypeOptionDataBuilder::new()
       .insert_i64_value("data_format", data.date_format.value())
       .insert_i64_value("data_format", data.date_format.value())
       .insert_i64_value("time_format", data.time_format.value())
       .insert_i64_value("time_format", data.time_format.value())
-      .insert_bool_value("include_time", data.include_time)
       .build()
       .build()
   }
   }
 }
 }
@@ -79,23 +77,26 @@ impl DateTypeOption {
   fn today_desc_from_timestamp(&self, cell_data: DateCellData) -> DateCellDataPB {
   fn today_desc_from_timestamp(&self, cell_data: DateCellData) -> DateCellDataPB {
     let timestamp = cell_data.timestamp.unwrap_or_default();
     let timestamp = cell_data.timestamp.unwrap_or_default();
     let include_time = cell_data.include_time;
     let include_time = cell_data.include_time;
+    let timezone_id = cell_data.timezone_id;
 
 
-    let naive = chrono::NaiveDateTime::from_timestamp_opt(timestamp, 0);
-    if naive.is_none() {
-      return DateCellDataPB::default();
-    }
-    let naive = naive.unwrap();
-    if timestamp == 0 {
-      return DateCellDataPB::default();
-    }
-    let fmt = self.date_format.format_str();
-    let date = format!("{}", naive.format_with_items(StrftimeItems::new(fmt)));
-
-    let time = if include_time {
-      let fmt = self.time_format.format_str();
-      format!("{}", naive.format_with_items(StrftimeItems::new(fmt)))
-    } else {
-      "".to_string()
+    let (date, time) = match cell_data.timestamp {
+      Some(timestamp) => {
+        let naive = chrono::NaiveDateTime::from_timestamp_opt(timestamp, 0).unwrap();
+        let offset = match Tz::from_str(&timezone_id) {
+          Ok(timezone) => timezone.offset_from_utc_datetime(&naive).fix(),
+          Err(_) => Local::now().offset().clone(),
+        };
+
+        let date_time = DateTime::<Local>::from_utc(naive, offset);
+
+        let fmt = self.date_format.format_str();
+        let date = format!("{}", date_time.format_with_items(StrftimeItems::new(fmt)));
+        let fmt = self.time_format.format_str();
+        let time = format!("{}", date_time.format_with_items(StrftimeItems::new(fmt)));
+
+        (date, time)
+      },
+      None => ("".to_owned(), "".to_owned()),
     };
     };
 
 
     DateCellDataPB {
     DateCellDataPB {
@@ -103,32 +104,9 @@ impl DateTypeOption {
       time,
       time,
       include_time,
       include_time,
       timestamp,
       timestamp,
+      timezone_id,
     }
     }
   }
   }
-
-  fn timestamp_from_utc_with_time(
-    &self,
-    naive_date: &NaiveDateTime,
-    time_str: &Option<String>,
-  ) -> FlowyResult<i64> {
-    if let Some(time_str) = time_str.as_ref() {
-      if !time_str.is_empty() {
-        let naive_time = chrono::NaiveTime::parse_from_str(time_str, self.time_format.format_str());
-
-        match naive_time {
-          Ok(naive_time) => {
-            return Ok(naive_date.date().and_time(naive_time).timestamp());
-          },
-          Err(_e) => {
-            let msg = format!("Parse {} failed", time_str);
-            return Err(FlowyError::new(ErrorCode::InvalidDateTimeFormat, &msg));
-          },
-        };
-      }
-    }
-
-    Ok(naive_date.timestamp())
-  }
 }
 }
 
 
 impl TypeOptionTransform for DateTypeOption {}
 impl TypeOptionTransform for DateTypeOption {}
@@ -167,39 +145,129 @@ impl CellDataChangeset for DateTypeOption {
     changeset: <Self as TypeOption>::CellChangeset,
     changeset: <Self as TypeOption>::CellChangeset,
     cell: Option<Cell>,
     cell: Option<Cell>,
   ) -> FlowyResult<(Cell, <Self as TypeOption>::CellData)> {
   ) -> FlowyResult<(Cell, <Self as TypeOption>::CellData)> {
-    let (timestamp, include_time) = match cell {
-      None => (None, false),
-      Some(cell) => {
-        let cell_data = DateCellData::from(&cell);
-        (cell_data.timestamp, cell_data.include_time)
+    // old date cell data
+    let (timestamp, include_time, timezone_id) = match cell {
+      None => (None, false, "".to_owned()),
+      Some(type_cell_data) => {
+        let cell_data = DateCellData::from(&type_cell_data);
+        (
+          cell_data.timestamp,
+          cell_data.include_time,
+          cell_data.timezone_id,
+        )
       },
       },
     };
     };
 
 
+    // update include_time and timezone_id if present
     let include_time = match changeset.include_time {
     let include_time = match changeset.include_time {
       None => include_time,
       None => include_time,
       Some(include_time) => include_time,
       Some(include_time) => include_time,
     };
     };
-    let timestamp = match changeset.date_timestamp() {
-      None => timestamp,
-      Some(date_timestamp) => match (include_time, changeset.time) {
-        (true, Some(time)) => {
-          let time = Some(time.trim().to_uppercase());
-          let naive = NaiveDateTime::from_timestamp_opt(date_timestamp, 0);
-          if let Some(naive) = naive {
-            Some(self.timestamp_from_utc_with_time(&naive, &time)?)
-          } else {
-            Some(date_timestamp)
+    let timezone_id = match changeset.timezone_id {
+      None => timezone_id,
+      Some(ref timezone_id) => timezone_id.to_owned(),
+    };
+
+    let previous_datetime = match timestamp {
+      Some(timestamp) => NaiveDateTime::from_timestamp_opt(timestamp, 0),
+      None => None,
+    };
+
+    let new_date_timestamp = changeset.date_timestamp();
+
+    // parse the time string, which would be in the local timezone
+    let parsed_time = match (include_time, changeset.time) {
+      (true, Some(time_str)) => {
+        let result = NaiveTime::parse_from_str(&time_str, self.time_format.format_str());
+        match result {
+          Ok(time) => Ok(Some(time)),
+          Err(_e) => {
+            let msg = format!("Parse {} failed", time_str);
+            Err(FlowyError::new(ErrorCode::InvalidDateTimeFormat, &msg))
+          },
+        }
+      },
+      _ => Ok(None),
+    }?;
+
+    // Calculate the new timestamp, while considering the timezone. If a new
+    // timestamp is included in the changeset without an accompanying time
+    // string, the new timestamp will simply overwrite the old one. Meaning,
+    // in order to change the day without time in the frontend, the time string
+    // must also be passed.
+    let timestamp = match Tz::from_str(&timezone_id) {
+      Ok(timezone) => match parsed_time {
+        Some(time) => {
+          // a valid time is provided, so we replace the time component of old
+          // (or new timestamp if provided) with this.
+          let local_date = match new_date_timestamp {
+            Some(timestamp) => Some(
+              timezone
+                .from_utc_datetime(&NaiveDateTime::from_timestamp_opt(timestamp, 0).unwrap())
+                .date_naive(),
+            ),
+            None => match previous_datetime {
+              Some(datetime) => Some(timezone.from_utc_datetime(&datetime).date_naive()),
+              None => None,
+            },
+          };
+
+          match local_date {
+            Some(date) => {
+              let local_datetime_naive = NaiveDateTime::new(date, time);
+              let local_datetime = timezone.from_local_datetime(&local_datetime_naive).unwrap();
+
+              Some(local_datetime.timestamp())
+            },
+            None => None,
           }
           }
         },
         },
-        _ => Some(date_timestamp),
+        None => match new_date_timestamp {
+          // no valid time, return old timestamp or new one if provided
+          Some(timestamp) => Some(timestamp),
+          None => timestamp,
+        },
+      },
+      Err(_) => match parsed_time {
+        // same logic as above, but using local time instead of timezone
+        Some(time) => {
+          let offset = Local::now().offset().clone();
+
+          let local_date = match new_date_timestamp {
+            Some(timestamp) => Some(
+              offset
+                .from_utc_datetime(&NaiveDateTime::from_timestamp_opt(timestamp, 0).unwrap())
+                .date_naive(),
+            ),
+            None => match previous_datetime {
+              Some(datetime) => Some(offset.from_utc_datetime(&datetime).date_naive()),
+              None => None,
+            },
+          };
+
+          match local_date {
+            Some(date) => {
+              let local_datetime = NaiveDateTime::new(date, time);
+              let datetime = offset.from_local_datetime(&local_datetime).unwrap();
+
+              Some(datetime.timestamp())
+            },
+            None => None,
+          }
+        },
+        None => match new_date_timestamp {
+          Some(timestamp) => Some(timestamp),
+          None => timestamp,
+        },
       },
       },
     };
     };
 
 
     let date_cell_data = DateCellData {
     let date_cell_data = DateCellData {
       timestamp,
       timestamp,
       include_time,
       include_time,
+      timezone_id,
     };
     };
-    Ok((date_cell_data.clone().into(), date_cell_data))
+    Ok((Cell::from(date_cell_data.clone()), date_cell_data))
   }
   }
 }
 }
 
 

+ 22 - 5
frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option_entities.rs

@@ -22,7 +22,7 @@ pub struct DateCellChangeset {
   pub date: Option<String>,
   pub date: Option<String>,
   pub time: Option<String>,
   pub time: Option<String>,
   pub include_time: Option<bool>,
   pub include_time: Option<bool>,
-  pub is_utc: bool,
+  pub timezone_id: Option<String>,
 }
 }
 
 
 impl DateCellChangeset {
 impl DateCellChangeset {
@@ -57,27 +57,37 @@ impl ToCellChangeset for DateCellChangeset {
 pub struct DateCellData {
 pub struct DateCellData {
   pub timestamp: Option<i64>,
   pub timestamp: Option<i64>,
   pub include_time: bool,
   pub include_time: bool,
+  pub timezone_id: String,
 }
 }
 
 
 impl From<&Cell> for DateCellData {
 impl From<&Cell> for DateCellData {
   fn from(cell: &Cell) -> Self {
   fn from(cell: &Cell) -> Self {
     let timestamp = cell
     let timestamp = cell
       .get_str_value(CELL_DATE)
       .get_str_value(CELL_DATE)
-      .map(|data| data.parse::<i64>().unwrap_or_default());
+      .map(|data| data.parse::<i64>().ok())
+      .flatten();
 
 
     let include_time = cell.get_bool_value("include_time").unwrap_or_default();
     let include_time = cell.get_bool_value("include_time").unwrap_or_default();
+    let timezone_id = cell.get_str_value("timezone_id").unwrap_or_default();
+
     Self {
     Self {
       timestamp,
       timestamp,
       include_time,
       include_time,
+      timezone_id,
     }
     }
   }
   }
 }
 }
 
 
 impl From<DateCellData> for Cell {
 impl From<DateCellData> for Cell {
   fn from(data: DateCellData) -> Self {
   fn from(data: DateCellData) -> Self {
+    let timestamp_string = match data.timestamp {
+      Some(timestamp) => timestamp.to_string(),
+      None => "".to_owned(),
+    };
     new_cell_builder(FieldType::DateTime)
     new_cell_builder(FieldType::DateTime)
-      .insert_str_value(CELL_DATE, data.timestamp.unwrap_or_default().to_string())
+      .insert_str_value(CELL_DATE, timestamp_string)
       .insert_bool_value("include_time", data.include_time)
       .insert_bool_value("include_time", data.include_time)
+      .insert_str_value("timezone_id", data.timezone_id)
       .build()
       .build()
   }
   }
 }
 }
@@ -105,6 +115,7 @@ impl<'de> serde::Deserialize<'de> for DateCellData {
         Ok(DateCellData {
         Ok(DateCellData {
           timestamp: Some(value),
           timestamp: Some(value),
           include_time: false,
           include_time: false,
+          timezone_id: "".to_owned(),
         })
         })
       }
       }
 
 
@@ -121,6 +132,7 @@ impl<'de> serde::Deserialize<'de> for DateCellData {
       {
       {
         let mut timestamp: Option<i64> = None;
         let mut timestamp: Option<i64> = None;
         let mut include_time: Option<bool> = None;
         let mut include_time: Option<bool> = None;
+        let mut timezone_id: Option<String> = None;
 
 
         while let Some(key) = map.next_key()? {
         while let Some(key) = map.next_key()? {
           match key {
           match key {
@@ -130,15 +142,20 @@ impl<'de> serde::Deserialize<'de> for DateCellData {
             "include_time" => {
             "include_time" => {
               include_time = map.next_value()?;
               include_time = map.next_value()?;
             },
             },
+            "timezone_id" => {
+              timezone_id = map.next_value()?;
+            },
             _ => {},
             _ => {},
           }
           }
         }
         }
 
 
-        let include_time = include_time.unwrap_or(false);
+        let include_time = include_time.unwrap_or_default();
+        let timezone_id = timezone_id.unwrap_or_default();
 
 
         Ok(DateCellData {
         Ok(DateCellData {
           timestamp,
           timestamp,
           include_time,
           include_time,
+          timezone_id,
         })
         })
       }
       }
     }
     }
@@ -203,7 +220,7 @@ impl DateFormat {
       DateFormat::Local => "%m/%d/%Y",
       DateFormat::Local => "%m/%d/%Y",
       DateFormat::US => "%Y/%m/%d",
       DateFormat::US => "%Y/%m/%d",
       DateFormat::ISO => "%Y-%m-%d",
       DateFormat::ISO => "%Y-%m-%d",
-      DateFormat::Friendly => "%b %d,%Y",
+      DateFormat::Friendly => "%b %d, %Y",
       DateFormat::DayMonthYear => "%d/%m/%Y",
       DateFormat::DayMonthYear => "%d/%m/%Y",
     }
     }
   }
   }

+ 3 - 2
frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_tests.rs

@@ -21,17 +21,18 @@ mod tests {
         &field_type,
         &field_type,
         &field
         &field
       ),
       ),
-      "Mar 14,2022"
+      "Mar 14, 2022"
     );
     );
 
 
     let data = DateCellData {
     let data = DateCellData {
       timestamp: Some(1647251762),
       timestamp: Some(1647251762),
       include_time: true,
       include_time: true,
+      timezone_id: "".to_owned(),
     };
     };
 
 
     assert_eq!(
     assert_eq!(
       stringify_cell_data(&data.into(), &FieldType::RichText, &field_type, &field),
       stringify_cell_data(&data.into(), &FieldType::RichText, &field_type, &field),
-      "Mar 14,2022"
+      "Mar 14, 2022"
     );
     );
   }
   }
 
 

+ 1 - 1
frontend/rust-lib/flowy-database2/tests/database/database_editor.rs

@@ -257,8 +257,8 @@ impl TestRowBuilder {
     let value = serde_json::to_string(&DateCellChangeset {
     let value = serde_json::to_string(&DateCellChangeset {
       date: Some(data.to_string()),
       date: Some(data.to_string()),
       time: None,
       time: None,
-      is_utc: true,
       include_time: Some(false),
       include_time: Some(false),
+      timezone_id: None,
     })
     })
     .unwrap();
     .unwrap();
     let date_field = self.field_with_type(&FieldType::DateTime);
     let date_field = self.field_with_type(&FieldType::DateTime);

+ 1 - 1
frontend/rust-lib/flowy-database2/tests/database/field_test/util.rs

@@ -48,8 +48,8 @@ pub fn make_date_cell_string(s: &str) -> String {
   serde_json::to_string(&DateCellChangeset {
   serde_json::to_string(&DateCellChangeset {
     date: Some(s.to_string()),
     date: Some(s.to_string()),
     time: None,
     time: None,
-    is_utc: true,
     include_time: Some(false),
     include_time: Some(false),
+    timezone_id: None,
   })
   })
   .unwrap()
   .unwrap()
 }
 }

+ 0 - 1
frontend/rust-lib/flowy-database2/tests/database/mock_data/board_mock_data.rs

@@ -43,7 +43,6 @@ pub fn make_test_board() -> DatabaseData {
         let date_type_option = DateTypeOption {
         let date_type_option = DateTypeOption {
           date_format: DateFormat::US,
           date_format: DateFormat::US,
           time_format: TimeFormat::TwentyFourHour,
           time_format: TimeFormat::TwentyFourHour,
-          include_time: false,
         };
         };
         let date_field = FieldBuilder::new(field_type.clone(), date_type_option)
         let date_field = FieldBuilder::new(field_type.clone(), date_type_option)
           .name("Time")
           .name("Time")

+ 0 - 1
frontend/rust-lib/flowy-database2/tests/database/mock_data/grid_mock_data.rs

@@ -44,7 +44,6 @@ pub fn make_test_grid() -> DatabaseData {
         let date_type_option = DateTypeOption {
         let date_type_option = DateTypeOption {
           date_format: DateFormat::US,
           date_format: DateFormat::US,
           time_format: TimeFormat::TwentyFourHour,
           time_format: TimeFormat::TwentyFourHour,
-          include_time: false,
         };
         };
         let date_field = FieldBuilder::new(field_type.clone(), date_type_option)
         let date_field = FieldBuilder::new(field_type.clone(), date_type_option)
           .name("Time")
           .name("Time")