DEV Community

LS
LS

Posted on

date conversion angular

What it’s doing (and why it shifts the date)
const dateObj = new Date(rawDateValue);                // if 'YYYY-MM-DD' → parsed as **UTC midnight**
const utcDate = new Date(Date.UTC(
  dateObj.getFullYear(), dateObj.getMonth(), dateObj.getDate()
));
const dateOnly = utcDate.toISOString().slice(0, 10);   // ISO in **UTC**
controlWithMetadata.control.setValue(dateOnly);        // now the control holds a **string**


If rawDateValue is 'YYYY-MM-DD', new Date('YYYY-MM-DD') is interpreted as UTC.

You then build another UTC date and call .toISOString() (still UTC).

Later, something (datepicker / form rebuild) re-parses that 'YYYY-MM-DD' string back to a Date. In JS, that string is again treated as UTC, but displayed in local time, so in US timezones it becomes the previous day.

Toggling edit mode runs this code again, compounding the effect → you observe -1 day (sometimes -2 around DST / multiple conversions).

Also, you’re changing the control type from a Date to a string, which makes the datepicker/serialization codepath even more fragile.

Minimal, targeted fix

Keep the control value as a Date (local midnight), never as a string; avoid toISOString() and avoid new Date('YYYY-MM-DD').

// Replace your cleanupDates implementation with this:
cleanupDates(taskAreaMember: TaskAreaMember) {
  if (taskAreaMember.fieldType !== 'date') return;

  taskAreaMember.controls.forEach((controlWithMetadata) => {
    const v = controlWithMetadata.control.value;
    if (!v) return;

    let d: Date;

    if (v instanceof Date) {
      // Normalize to local midnight, preserving Y/M/D only.
      d = new Date(v.getFullYear(), v.getMonth(), v.getDate());
    } else if (typeof v === 'string') {
      // Expecting 'YYYY-MM-DD' → construct **local** date (NOT new Date(ymd))
      const [y, m, dStr] = v.split('-').map(Number);
      d = new Date(y, (m || 1) - 1, dStr || 1); // local midnight
    } else {
      return;
    }

    controlWithMetadata.control.setValue(d, { emitEvent: false });
  });
}

And when you build the payload (save/merge path)

Convert from Date → 'YYYY-MM-DD' explicitly, without toISOString():

function toYmd(date: Date): string {
  const y = date.getFullYear();
  const m = String(date.getMonth() + 1).padStart(2, '0');
  const d = String(date.getDate()).padStart(2, '0');
  return `${y}-${m}-${d}`;
}

// wherever you currently read control.value for birth date:
const val = control.value as Date;
payload.birthDate = toYmd(val);

If you prefer Moment/Material UTC adapter

You can also standardize with Moment’s adapter and useUtc: true, but the core rule remains:

Never call new Date('YYYY-MM-DD')

Never use .toISOString() for birthdays or date-only values

Keep the form control as a Date (or a Moment) and serialize to 'YYYY-MM-DD' only at save time.

Why this will stop the drift

Local-midnight Date(y, m, d) has no timezone surprises when displayed.

You aren’t converting to ISO/UTC and back anymore.

The control type stays consistent (Date ↔ Date), so re-entering edit mode won’t re-interpret a string as UTC.

If you show me the exact save path where you read the birthdate control, I’ll give you the precise 2–3 line diff to swap in toYmd(...) there too.
Enter fullscreen mode Exit fullscreen mode

Top comments (0)