Extract format from ISO 8601 string date #1119
-
I would like to start by saying thank you @icambron for your hard work, amazing lib. I have a question and am wondering if anyone could point me in the right direction. Is there an easy way to accomplish the following // These isoDateString could be any variations of ISO 8601
const dynamicIsoDateString = '2022-01-02';
// format: 'yyyy-MM-dd'
// parseIsoFormat is what I am after, is there a utility function somewhere that does that?
// I found something similar in regexParser.js but I don't think it's a public export and that
// looks like it's returning the parts of the date, not the format per se.
// https://github.com/moment/luxon/blob/master/src/impl/regexParser.js#L278
const format = parseIsoFormat(dynamicIsoDateString);
// isoDateString: '2022-01-14'
const isoDateString = DateTime.now().toFormat(format); This came up while trying to handle APIs that use ISO 8601 in various formats and I can't know ahead of time which of the ISO 8601 formats are used until I get a sample date string. Consuming dates is fine using Any help would be greatly appreciated. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
Luxon doesn't have anything like that built in. The ISO parser, as you noticed, is a big regex. It doesn't have a way to translate the matching parts back to format tokens because the regex only captures as much information as is needed to build the DateTime. So the info of exactly which ISO variant was used is lost. So I think you'll need a non-Luxon-based approach, where you separately parse the format and keep that format with the DateTime until you need to serialize. I'll leave it to other commenters to suggest how to best do that, and whether there are good libraries to do it with. |
Beta Was this translation helpful? Give feedback.
-
I didn't find any readily available solution for our particular problem, so I ended up writing the following couple of functions to parse the format out of a sample ISO 8601 string in case that can help anyone else facing the same issue, @icambron let me know if that particular functionality would be useful to integrate into luxon. All of the regex were taken from the regexParser and tweaked. export function getISO8601Format(
iso8601string: string
): string {
const offsetRegex = /(?:(?<offsetZ>Z)|(?<offsetHours>[+-]\d\d)(?:(?<offsetDelimiter>:?)(?<offsetMinutes>\d\d))?)/,
isoTimeBaseRegex = /(?<hours>\d\d)(?:(?<timeDelimiter>:?)(?<minutes>\d\d)(?::?(?<seconds>\d\d)(?:(?<msDelimiter>[.,])(?<miliseconds>\d{1,30}))?)?)?/,
isoTimeRegex = RegExp(`${isoTimeBaseRegex.source}${offsetRegex.source}?`),
isoTimeExtensionRegex = RegExp(`(?:(?<timeSeparator>T)${isoTimeRegex.source})?`),
isoYmdRegex = /(?<year>[+-]\d{6}|\d{4})(?:(?<dateSeparator>-?)(?<month>\d\d)(?:-?(?<day>\d\d))?)?/,
isoWeekRegex = /(?<year>\d{4})(?<dateSeparator>-?)W(?<week>\d\d)(?:-?(?<weekDay>\d))?/,
isoOrdinalRegex = /(?<year>\d{4})(?<dateSeparator>-?)(?<dayOfYear>\d{3})/;
const isoYmdWithTimeExtensionRegex = new RegExp(`^${isoYmdRegex.source}${isoTimeExtensionRegex.source}$`);
const isoWeekWithTimeExtensionRegex = new RegExp(`^${isoWeekRegex.source}${isoTimeExtensionRegex.source}$`);
const isoOrdinalWithTimeExtensionRegex = new RegExp(`^${isoOrdinalRegex.source}${isoTimeExtensionRegex.source}$`);
if (isoYmdWithTimeExtensionRegex.test(iso8601string)) {
const matches = isoYmdWithTimeExtensionRegex.exec(iso8601string);
const dateSeparator = matches.groups['dateSeparator'] || '';
const month = matches.groups['month'] != null ? `${dateSeparator}MM` : '';
const day = matches.groups['day'] != null ? `${dateSeparator}dd` : '';
const timeSeparator = !!matches.groups['timeSeparator'] ? `'T'` : '';
const time = getISO8601TimeFormat(matches.groups);
return `yyyy${month}${day}${timeSeparator}${time}`;
} else if (isoWeekWithTimeExtensionRegex.test(iso8601string)) {
const matches = isoWeekWithTimeExtensionRegex.exec(iso8601string);
const dateSeparator = matches.groups['dateSeparator'] || '';
const weekDay = !!matches.groups['weekDay'] ? `${dateSeparator}E` : '';
const timeSeparator = !!matches.groups['timeSeparator'] ? `'T'` : '';
const time = getISO8601TimeFormat(matches.groups);
return `kkkk${dateSeparator}'W'WW${weekDay}${timeSeparator}${time}`;
} else if (isoOrdinalWithTimeExtensionRegex.test(iso8601string)) {
const matches = isoOrdinalWithTimeExtensionRegex.exec(iso8601string);
const dateSeparator = matches.groups['dateSeparator'] || '';
const timeSeparator = !!matches.groups['timeSeparator'] ? `'T'` : '';
const time = getISO8601TimeFormat(matches.groups);
return `yyyy${dateSeparator}ooo${timeSeparator}${time}`;
}
throw new Error('Unsupported ISO 8601 format');
}
function getISO8601TimeFormat(groups: RegExpExecArray['groups']) {
let format = '';
if (groups['hours'] != null) {
const timeDelimiter = groups['timeDelimiter'] || '';
const minutes = groups['minutes'] != null ? `${timeDelimiter}mm` : '';
const seconds = groups['seconds'] != null ? `${timeDelimiter}ss` : '';
const miliseconds = groups['miliseconds'] != null ? `${groups['msDelimiter']}SSS` : '';
let offset = '';
if (groups['offsetZ'] != null) {
offset = `'Z'`;
} else if (groups['offsetHours'] != null) {
offset = !!groups['offsetDelimiter'] ? 'ZZ' : 'ZZZ';
}
format = `HH${minutes}${seconds}${miliseconds}${offset}`;
}
return format;
} |
Beta Was this translation helpful? Give feedback.
Luxon doesn't have anything like that built in. The ISO parser, as you noticed, is a big regex. It doesn't have a way to translate the matching parts back to format tokens because the regex only captures as much information as is needed to build the DateTime. So the info of exactly which ISO variant was used is lost.
So I think you'll need a non-Luxon-based approach, where you separately parse the format and keep that format with the DateTime until you need to serialize. I'll leave it to other commenters to suggest how to best do that, and whether there are good libraries to do it with.