npm package: @salesflare/date-format-infer
Description: Infer date formats from a list of date strings. Handles ambiguous formats (DD/MM vs MM/DD) by analyzing multiple samples.
Author: Salesflare Team
Latest version: 0.3.0

# date-format-infer

A lightweight library to infer date formats from a list of date strings. Useful for data import features where date columns need to be normalized to a standard format (e.g., ISO 8601).

## The Problem

When importing data with date columns, you often encounter ambiguous formats:
- Is `01/02/2023` January 2nd (MM/DD/YYYY) or February 1st (DD/MM/YYYY)?
- The library solves this by analyzing multiple date values from a column to determine the format.

## Installation

```bash
npm install date-format-infer
```

## Quick Start

```javascript
const { inferDateFormat, convertToISO } = require('date-format-infer');

// Analyze dates from a column
const result = inferDateFormat(['25/12/2023', '15/01/2024', '30/06/2023']);

if (result.confident) {
  console.log(`Format: ${result.format}`); // 'DD/MM/YYYY'
  
  // Convert to ISO format
  const iso = convertToISO('25/12/2023', result.format);
  console.log(iso); // '2023-12-25'
} else {
  console.log('Ambiguous, could be:', result.possibleFormats);
}
```

## How It Works

1. Tests each date string against known format patterns
2. Eliminates formats that don't match
3. When a date has `day > 12`, it disambiguates DD vs MM position
4. Returns early when confident (single format remaining)
5. Falls back to natural language parsing via [chrono-node](https://github.com/wanasit/chrono) if enabled

## API

### `inferDateFormat(dates, options?)`

Infers the date format from an array of date strings.

```typescript
interface InferResult {
  format: string | null;       // The inferred format (e.g., 'DD/MM/YYYY')
  possibleFormats: string[];   // All formats that could match
  confident: boolean;          // True if format is unambiguous
  isNaturalLanguage: boolean;  // True if dates are natural language
}

interface InferOptions {
  maxSamples?: number;           // Max dates to check (default: 100)
  allowNaturalLanguage?: boolean; // Try chrono-node fallback (default: true)
  locale?: SupportedLocale;       // Locale for natural language parsing (default: 'en')
}

type SupportedLocale = 'en' | 'nl' | 'fr' | 'de' | 'es' | 'pt' | 'ja' | 'zh' | 'ru' | 'uk' | 'it' | 'sv';
```

**Examples:**

```javascript
// Unambiguous (day > 12 disambiguates)
inferDateFormat(['25/12/2023', '30/06/2024']);
// { format: 'DD/MM/YYYY', confident: true, ... }

// Ambiguous (all days ≤ 12)
inferDateFormat(['01/02/2023', '03/04/2024']);
// { format: null, confident: false, possibleFormats: ['DD/MM/YYYY', 'MM/DD/YYYY'] }

// Natural language
inferDateFormat(['next Monday', 'last Friday']);
// { format: null, confident: true, isNaturalLanguage: true }

// Natural language in other languages (French)
inferDateFormat(['13 Janvier 2026', '14 Février 2026'], { locale: 'fr' });
// { format: null, confident: true, isNaturalLanguage: true }
```

### `parseDate(dateStr, pattern)`

Parse a date string using a specific format pattern.

```javascript
parseDate('25/12/2023', 'DD/MM/YYYY');
// { year: 2023, month: 12, day: 25 }

parseDate('25/12/2023 14:30:00', 'DD/MM/YYYY HH:mm:ss');
// { year: 2023, month: 12, day: 25, hour: 14, minute: 30, second: 0 }
```

### `convertToISO(dateStr, pattern)`

Convert a date string to ISO format using the specified pattern.

```javascript
convertToISO('25/12/2023', 'DD/MM/YYYY');
// '2023-12-25'

convertToISO('25/12/2023 14:30:00', 'DD/MM/YYYY HH:mm:ss');
// '2023-12-25T14:30:00'
```

### `parseNaturalDate(dateStr, locale?)` / `convertNaturalToISO(dateStr, locale?)`

Parse natural language dates using chrono-node with multi-language support.

The locale parameter specifies a preferred language to try first (after English). The parser automatically falls back through all supported locales until one succeeds.

```javascript
// English (default)
parseNaturalDate('December 25, 2023 at 2:30 PM');
// { year: 2023, month: 12, day: 25, hour: 14, minute: 30 }

convertNaturalToISO('December 25, 2023');
// '2023-12-25T12:00:00.000'

// French
parseNaturalDate('13 Janvier 2026', 'fr');
// { year: 2026, month: 1, day: 13 }

convertNaturalToISO('13 Janvier 2026', 'fr');
// '2026-01-13...'

// Spanish
parseNaturalDate('13 Enero 2026', 'es');
// { year: 2026, month: 1, day: 13 }

// German (note: requires period after day number)
parseNaturalDate('13. Januar 2026', 'de');
// { year: 2026, month: 1, day: 13 }

// Dutch (note: lowercase month names)
parseNaturalDate('13 januari 2026', 'nl');
// { year: 2026, month: 1, day: 13 }
```

**Supported locales:** `en`, `nl`, `fr`, `de`, `es`, `pt`, `ja`, `zh`, `ru`, `uk`, `it`, `sv`

**Note:** Each language may have specific formatting requirements (e.g., German requires a period after the day number, Dutch is case-sensitive for month names).

## Supported Formats

### Date Only

| Pattern | Example |
|---------|---------|
| `YYYY-MM-DD` | `2023-12-25` |
| `DD/MM/YYYY` | `25/12/2023` |
| `MM/DD/YYYY` | `12/25/2023` |
| `DD/MM/YY` | `25/12/23` |
| `MM/DD/YY` | `12/25/23` |
| `DD MMM YYYY` | `25 Dec 2023` |
| `MMM DD YYYY` | `Dec 25, 2023` |
| `YYYYMMDD` | `20231225` |

### Date with Time

| Pattern | Example |
|---------|---------|
| `YYYY-MM-DDTHH:mm:ss` | `2023-12-25T14:30:00` |
| `YYYY-MM-DDTHH:mm:ss.SSS` | `2023-12-25T14:30:00.123` |
| `DD/MM/YYYY HH:mm` | `25/12/2023 14:30` |
| `DD/MM/YYYY HH:mm:ss` | `25/12/2023 14:30:00` |
| `DD/MM/YYYY h:mm A` | `25/12/2023 2:30 PM` |
| `MM/DD/YYYY HH:mm:ss` | `12/25/2023 14:30:00` |

### With Timezone

| Pattern | Example |
|---------|---------|
| `YYYY-MM-DDTHH:mm:ssZ` | `2023-12-25T14:30:00Z` |
| `YYYY-MM-DDTHH:mm:ssZ` | `2023-12-25T14:30:00+05:30` |
| `YYYY-MM-DDTHH:mm:ss.SSSZ` | `2023-12-25T14:30:00.123Z` |

**Note:** Separators (`/`, `-`, `.`) are treated equivalently.

## TypeScript

Full TypeScript support with exported types:

```typescript
import { 
  inferDateFormat, 
  parseDate, 
  convertToISO,
  parseNaturalDate,
  convertNaturalToISO,
  InferResult, 
  InferOptions, 
  ParsedDate,
  SupportedLocale
} from 'date-format-infer';
```

## Assumptions

- **ISO 8601 is unambiguous**: `YYYY-MM-DD` is assumed to be year-month-day (not year-day-month)
- **Consistent column format**: All dates in a column use the same format
- **Reasonable dates**: Years are between 1-9999, validated for days in month

## License

MIT