mymth / vanillajs-datepicker

A vanilla JavaScript remake of bootstrap-datepicker for Bulma and other CSS frameworks
MIT License
744 stars 154 forks source link

Date value changes from toValue and toDisplay #138

Closed adiakritos closed 1 year ago

adiakritos commented 1 year ago

Hello MyMth!

This is an awesome gem that I'm using often with my projects - thank you for your dedication to maintaining it.

I think I read somewhere that there's a version 2 coming? I'd love to stay on top of that release.

I'm having some trouble understanding how to handle dates that are provided to the DatePicker, and ensuring they are transformed into the format I need on display, and also that the date that's saved as a value to the input element is in the format I need to pass via form submission.

If there's a better place to discuss I'm happy to do so.

Here's the current challenge:

I have a record with a datetime value that is passed to the datepicker. The datetime is automatically saved in UTC.

2023-03-01 00:00:00 UTC

When this value is provided to the DatePicker it displayed as:

"February 2023"

The way I'm handling the formatting is like so:

 format: {
          toValue(date, format, locale) {
            let parsed_date = Datepicker.parseDate(Date.parse(date), "MM y", locale)
            return parsed_date;
          },
          toDisplay(date, format, locale) {
            let formatted_date = Datepicker.formatDate(date, "MM y", locale);
            return formatted_date;
          }
        },             

My understanding is that the toValue method needs to return a date object or number, and then this is passed to the formatted toDisplay method afterward.

If I inspect the date value passed to toValue - it is the correct datetime string. If I simply do Date.parse(date) it will convert that into the number version. When I inspect the toDisplay date object it appears the date was converted to EST.

So my assumption is that I need to transform that date version back to UTC, then provide the datetime string to Datepciker.formateDate like I already am.

But the problem is that I don't know how to reverse this transformation ( or prevent it from happening ) after it's been passed to toDisplay.

Here is similar code to above with the console calls and the corresponding output.

        format: {
          toValue(date, format, locale) {
            console.log('date', date);
            console.log('Date.parse(date)', Date.parse(date));
            console.log('Date.parse(date).toUTCString()', new Date(date).toUTCString());
            return new Date(date);
          },
          toDisplay(date, format, locale) {
            console.log(date);
            console.log(new Date(date).toUTCString());
            let formatted_date = Datepicker.formatDate(date, this.hasFormatValue ? this.formatValue : "MM y", locale);
            console.log(formatted_date);
            return formatted_date;
          }
        },

Screen Shot 2023-02-19 at 4 51 31 PM

You can see that the date value is actually different after it is passed.

Is this expected behavior? Am I missing something?

adiakritos commented 1 year ago

It appears that the function parseTime uses this function:

export function stripTime(timeValue) {
  return new Date(timeValue).setHours(0, 0, 0, 0);
}

... and that changes the date value.

In this case, all I really need is to month and year values, and the day of the month to default to the last day of the month. So I think I should figure out how to use setDate because you've mentioned this before.

I'll post my updates here.

mymth commented 1 year ago

As being stated here, https://mymth.github.io/vanillajs-datepicker/#/overview?id=providing-date this date picker converts all given dates to the internal date value: the time value of the date's local time 00:00:00.000.

So, I think (you may have noticed already, but just in case) doing something like this will probably work.

const localDate = new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
datepicker.setDate(localDate);

As for v2, I haven't started anything yet. (the next release is likely to be v2, though.) I guess someone saw my comment saying like "it will be a breaking change of a fundamental part, thus will be changed in v2", and mistook it as "v2 is coming."

adiakritos commented 1 year ago

I wasn't able to make setDate work. But I was able to get it to work by simply formatting the value in the HTML rather than formatting it from datepicker. This way the display value is what I want it to be when I load and edit form. Here is what I tried from the javascript side of things:

  setupDateInput() {
    let datePickerConfigs = {
        buttonClass: 'btn',
        autohide: true,
        format: {
          toValue(date, format, locale) {
            return new Date(date);
          },
          toDisplay(date, format, locale) {
            return Datepicker.formatDate(date, this.hasFormatValue ? this.formatValue : "MM y", locale);
          }
        },
        startView: 1
    }

    if (this.hasMaxViewValue) {
      datePickerConfigs.maxView = this.maxViewValue
    }

    if (this.hasPickLevelValue) {
      datePickerConfigs.pickLevel = this.pickLevelValue
    }

    let datepicker = this.applyDatePickerFor(this.dateInputTarget, datePickerConfigs)

    if (this.dateInputTarget.value) {
      let date = new Date(Date.parse(this.dateInputTarget.value))
      let localDate = new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
      datepicker.setDate(localDate);
    }
  }

  applyDatePickerFor(field, configs){
    const datepicker = new Datepicker(field, configs);
    return datepicker;
  }

The behavior I'm seeing with this javascript is beyond what the datepicker is doing. The value shows up in the input with January 1 2023 in UTC format - the datetime value from my DB. But when I inspect the input value before the datepicker is applied, it returns "December 2022". I don't understand why it's formatting it like this if the literal input value is a datetime value and the datepicker hasn't be applied yet. So what I did was the following and it works.

Here's the solution in my Rails application from the HTML point of view:


            <%= form.text_field :deadline, {
              class: "form-control",
              placeholder: "Period",
              readonly: true,
              **value: form.object.deadline.strftime("%B %Y"),**
              data: {
                datepicker_target: 'dateInput'
              }
            } %>