Skip to content

Instantly share code, notes, and snippets.

@SippieCup
Last active June 30, 2026 20:30
Show Gist options
  • Select an option

  • Save SippieCup/657cca1da2db92bc6e6c96632bf93962 to your computer and use it in GitHub Desktop.

Select an option

Save SippieCup/657cca1da2db92bc6e6c96632bf93962 to your computer and use it in GitHub Desktop.
luxon support for primeng
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-empty-function */
import { Directive, OnInit, inject, input } from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';
import { DateTime, Settings } from 'luxon';
@Directive({
// eslint-disable-next-line angular/directive-selector
selector: '[luxonDate]', // Ensure it only applies to p-datepicker
standalone: true,
})
export class LuxonDateDirective implements ControlValueAccessor, OnInit {
overrideZone = input<string | undefined>(); // Optional override for timezone
overrideLocale = input<string | undefined>(); // Optional override for locale
private ngControl = inject(NgControl, { optional: true, self: true });
private onChange: (value: any) => void = () => {};
private onTouched: () => void = () => {};
// A reference to the original value accessor from p-datepicker
private originalAccessor: ControlValueAccessor | null = null;
constructor() {
if (this.ngControl) {
// Save the original value accessor and replace it with this directive
this.originalAccessor = this.ngControl.valueAccessor;
this.ngControl.valueAccessor = this;
}
}
ngOnInit(): void {
if (!this.originalAccessor) {
console.warn('LuxonDateDirective: No original accessor found on NgControl.');
}
}
// Converts JavaScript Date to Luxon DateTime<true> or null
private toLuxon(input: Date | Date[] | null): DateTime<true> | DateTime<true>[] | null {
if (!input) return null;
if (Array.isArray(input)) {
return input
.map((d) => {
const dt = DateTime.fromJSDate(d, {
zone: this.overrideZone() || Settings.defaultZone,
});
return dt.isValid ? (dt as DateTime<true>) : null;
})
.filter(Boolean) as DateTime<true>[];
} else {
const dt = DateTime.fromJSDate(input, {
zone: this.overrideZone() || Settings.defaultZone,
});
return dt.isValid ? (dt as DateTime<true>) : null;
}
}
// Converts a valid Luxon DateTime<true> to JavaScript Date or null
private toJsDate(input: DateTime<true> | DateTime<true>[] | null): Date | Date[] | null {
if (!input) return null;
if (Array.isArray(input)) {
return input.map((dt) => (dt ? dt.toJSDate() : null)).filter(Boolean) as Date[];
} else {
return input ? input.toJSDate() : null;
}
}
// Intercept the `writeValue` call and convert Luxon to JS Date
writeValue(value: any): void {
const jsValue = Array.isArray(value)
? this.toJsDate(value as DateTime<true>[])
: this.toJsDate(value as DateTime<true>);
if (this.originalAccessor) {
this.originalAccessor.writeValue(jsValue);
}
}
// Intercept the `registerOnChange` call and handle Luxon conversion
registerOnChange(fn: (value: any) => void): void {
this.onChange = (jsValue: Date | Date[] | null) => {
const luxonValue = this.toLuxon(jsValue);
fn(luxonValue);
};
if (this.originalAccessor) {
this.originalAccessor.registerOnChange((value: any) => {
this.onChange(value);
});
}
}
// Delegate the `registerOnTouched` to the original accessor
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
if (this.originalAccessor) {
this.originalAccessor.registerOnTouched(fn);
}
}
// Delegate the `setDisabledState` to the original accessor
setDisabledState(isDisabled: boolean): void {
if (this.originalAccessor && this.originalAccessor.setDisabledState) {
this.originalAccessor.setDisabledState(isDisabled);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment