Enable Dark Mode!
how-to-create-date-widgets-in-odoo-19.jpg
By: Muhammed Fahis V P

How to Create Date Widgets in Odoo 19

Technical Odoo 19 Fields and widgets

Fields in Odoo specify the format of data models and specify the kinds of data that each record can hold.  Date and Datetime fields are among the most important field types for managing time-sensitive tasks, including project tracking, deadline management, and scheduling.

Thanks to improved widgets that enhance the way date and time values are shown and interacted with in the user interface, these fields have become even more dynamic in Odoo 19.

This blog will walk through Odoo 19's Date and Datetime field types and their useful widgets.

Date:

The Date field type is used to store only the date portion (without time). It displays a calendar picker that allows users to choose a date effortlessly.

Python Definition:

published_date = fields.Date(string="Published Date")

Xml usage:

<field name="published_date"/>

This creates a date field named Published Date that stores values in the YYYY-MM-DD format.

Widgets

  • remaining_days
  • The remaining_days widget displays how many days remain until the selected date, based on today’s date.

    This is very useful for expiry tracking, project deadlines, or reminder-based workflows.

    <field name="published_date" widget="remaining_days"/>

    If today’s date is October 18, 2025, and the selected date is October 22, 2025, the field will display:

    “In 4 days”

    If the selected date has already passed, it will show:

    “2 days ago”

    This widget automatically calculates the difference between the current date and the field value, offering quick visual insight without additional computations.

    (Insert screenshot showing the remaining_days widget output)

    Datetime:

    When you need to capture both date and time, Odoo provides the Datetime field type. This field is ideal for logging events, recording timestamps, or managing time-based records such as orders, meetings, or shifts.

    Python Definition:

    order_date = fields.Datetime(string="Order Date")

    This allows users to pick both a date and time from a single field.

    Widgets

    • date
    • The date widget can be applied to a Datetime field to display only the date portion, hiding the time value.

    This is helpful when the backend needs to store time but the end user only needs to see the date.

    <field name="order_date" widget="date"/>
    • remaining_days
    • Just like with Date fields, the remaining_days widget can also be applied to Datetime fields.

      <field name="order_date" widget="remaining_days"/>

      This widget is the same as for the date field. This widget calculates the difference between the current date and the stored datetime value, then displays it in a human-readable format such as: “In 3 days” or “5 days ago

      • daterange

      The daterange widget is a convenient way to allow users to select a start and end date or datetime range directly within the form. This is especially useful for project durations, leave applications, booking systems, or reporting filters.

      <field name="date_begin" widget="daterange" string="From"
             class="oe_inline" options="{'related_end_date': 'date_end'}"/>
      <field name="date_end" widget="daterange" string="To"
             class="oe_inline" options="{'related_start_date': 'date_begin'}"/>

      Here, the first field (date_begin) is linked to the second field (date_end) through widget options, ensuring a consistent range selection.

      Creating a custom date widget

      Now we are going to create a custom inline calendar date widget. Display an inline calendar directly within a form. Make it visually appealing and user-friendly.

      Step 1: Create the JS Widget

      Create a new js file under:

      custom_module/static/src/js/inline_calendar_widget.js

      ** @odoo-module **/
      import { Component, useState } from "@odoo/owl";
      import { registry } from "@web/core/registry";
      import { standardFieldProps } from "@web/views/fields/standard_field_props";
      import { parseDate } from "@web/core/l10n/dates";
      export class InlineCalendar extends Component {
         static template = "inline_calendar.Template";
         static props = { ...standardFieldProps };
         setup() {
             const today = new Date();
             this.state = useState({
                 month: today.getMonth(),
                 year: today.getFullYear(),
             });
         }
         get days() {
             const firstDay = new Date(this.state.year, this.state.month, 1).getDay();
             const totalDays = new Date(this.state.year, this.state.month + 1, 0).getDate();
             const days = [];
             for (let i = 0; i < firstDay; i++) days.push(null);
             for (let i = 1; i <= totalDays; i++) days.push(i);
             return days;
         }
         get selected() {
             const val = this.props.record.data[this.props.name];
             if (!val || !val.toFormat) return null;
             const dateStr = val.toFormat('yyyy-MM-dd');
             const date = new Date(dateStr);
             if (date.getMonth() === this.state.month && date.getFullYear() === this.state.year) {
                 return date.getDate();
             }
             return null;
         }
         getClass(day) {
             if (!day) return 'empty';
             const today = new Date();
             const date = new Date(this.state.year, this.state.month, day);
             today.setHours(0, 0, 0, 0);
             date.setHours(0, 0, 0, 0);
             let cls = 'day';
             if (day === this.selected) cls += ' selected';
             if (date.getTime() === today.getTime()) cls += ' today';
             if (date < today) cls += ' past';
             if (date > today) cls += ' future';
             return cls;
         }
         select(day) {
             if (!day || this.props.readonly) return;
             const dateStr = `${this.state.year}-${String(this.state.month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
             this.props.record.update({ [this.props.name]: parseDate(dateStr) });
         }
         prev() {
             this.state.month === 0 ? (this.state.month = 11, this.state.year--) : this.state.month--;
         }
         next() {
             this.state.month === 11 ? (this.state.month = 0, this.state.year++) : this.state.month++;
         }
      }
      registry.category("fields").add("inline_calendar", {
         component: InlineCalendar,
         supportedTypes: ["date"],
      });

      Step 2: Create the QWeb Template

      Create an Xml file under:

      custom_module/static/src/xml/inline_calendar_widget.xml

      <?xml version="1.0" encoding="UTF-8"?>
      <templates>
         <t t-name="inline_calendar.Template">
             <div class="simple-color-date">
                 <div class="header">
                     <button t-on-click="prev">?</button>
                     <span><t t-esc="state.month + 1"/> / <t t-esc="state.year"/></span>
                     <button t-on-click="next">?</button>
                 </div>
                 <div class="grid">
                     <t t-foreach="days" t-as="d" t-key="d_index">
                         <div t-att-class="getClass(d)" t-on-click="() => this.select(d)">
                             <t t-if="d" t-esc="d"/>
                         </div>
                     </t>
                 </div>
             </div>
         </t>

      Step 3: Creating CSS

      We can also style our widget using css. For that, we can add a file under

      custom_module/static/src/cssl/inline_calendar_widget.css

      .simple-color-date {
         width: 250px;
         padding: 10px;
         border: 1px solid #ddd;
         border-radius: 4px;
         background: white;
      }
      .header {
         display: flex;
         justify-content: space-between;
         margin-bottom: 10px;
         font-weight: bold;
      }
      .header button {
         border: none;
         background: #f5f5f5;
         padding: 5px 10px;
         cursor: pointer;
         border-radius: 3px;
      }
      .grid {
         display: grid;
         grid-template-columns: repeat(7, 1fr);
         gap: 2px;
      }
      .day {
         aspect-ratio: 1;
         display: flex;
         align-items: center;
         justify-content: center;
         cursor: pointer;
         font-size: 13px;
         border-radius: 3px;
      }
      .day:hover {
         background: #f0f0f0;
      }
      .day.empty {
         cursor: default;
      }
      .day.today {
         color: #28a745;
         font-weight: bold;
      }
      .day.past {
         color: #dc3545;
      }
      .day.future {
         color: #007bff;
      }
      .day.selected {
         background: #333;
         color: white;
      }
      */

      Step 4: Register the Widget in Manifest

      "assets": {
             "web.assets_backend": [
                 "custom_module/static/src/js/inline_calendar_widget.js",
                 "custom_module/static/src/xml/inline_calendar_widget.xml",
                 "custom_module/static/src/css/inline_calendar_widget.css",
             ],
         },

      How to Create Date Widgets in Odoo 19-cybrosys

      Odoo 19 continues to refine the user experience with smarter and more interactive field widgets.

      The remaining_days widget is a simple yet powerful addition for instantly tracking deadlines and future events, while the daterange widget simplifies selecting date intervals for planning and reporting.

      By effectively using these widgets, developers and functional consultants can create more intuitive and insightful views, helping users manage their time-based data efficiently.

      To read more about How to create a view widget in Odoo 18, refer to our blog How to create a view widget in Odoo 18.


If you need any assistance in odoo, we are online, please chat with us.



0
Comments



Leave a comment



whatsapp_icon
location

Calicut

Cybrosys Technologies Pvt. Ltd.
Neospace, Kinfra Techno Park
Kakkancherry, Calicut
Kerala, India - 673635

location

Kochi

Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.

location

Bangalore

Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message