Skip to content

ORA-01843 Not a Valid Month - Fix Oracle Month Format Errors

Error Text: ORA-01843: not a valid month

This error occurs when Oracle cannot recognize the month portion of a date string during conversion. Common causes include invalid month values (like 13), incorrect month names, language/locale mismatches, or wrong date format masks.

Month Format Options
├── MM - Numeric month (01-12)
├── MON - Abbreviated month name (JAN, FEB)
├── MONTH - Full month name (JANUARY, FEBRUARY)
├── RM - Roman numeral month (I-XII)
└── Language-specific variations
  • Invalid month numbers - Values outside 01-12 range
  • Wrong month names - Typos or incorrect abbreviations
  • Language mismatches - English months with non-English database
  • Format confusion - DD/MM vs MM/DD formats
  • Case sensitivity - Month names in wrong case
-- Check for invalid numeric months
SELECT
date_string,
SUBSTR(date_string, 6, 2) as month_part,
CASE
WHEN TO_NUMBER(SUBSTR(date_string, 6, 2)) BETWEEN 1 AND 12 THEN 'Valid'
ELSE 'Invalid month: ' || SUBSTR(date_string, 6, 2)
END as month_check
FROM import_table
WHERE REGEXP_LIKE(date_string, '^\d{4}-\d{2}-\d{2}')
AND (TO_NUMBER(SUBSTR(date_string, 6, 2)) < 1
OR TO_NUMBER(SUBSTR(date_string, 6, 2)) > 12);
-- Check current NLS settings
SELECT
parameter,
value
FROM nls_session_parameters
WHERE parameter IN ('NLS_DATE_FORMAT', 'NLS_DATE_LANGUAGE', 'NLS_TERRITORY');
-- Test month name recognition
DECLARE
v_date DATE;
v_months SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST(
'JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN',
'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'
);
BEGIN
FOR i IN 1..v_months.COUNT LOOP
BEGIN
v_date := TO_DATE('15-' || v_months(i) || '-2024', 'DD-MON-YYYY');
DBMS_OUTPUT.PUT_LINE(v_months(i) || ' - Valid');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(v_months(i) || ' - Error: ' || SQLERRM);
END;
END LOOP;
END;
/
-- Detect month format in strings
CREATE OR REPLACE FUNCTION detect_month_format(
p_date_string VARCHAR2
) RETURN VARCHAR2 AS
v_month_part VARCHAR2(20);
BEGIN
-- Check for numeric month (MM)
IF REGEXP_LIKE(p_date_string, '\d{2}[-/]\d{2}[-/]\d{4}') THEN
v_month_part := REGEXP_SUBSTR(p_date_string, '\d{2}', 1, 2);
IF TO_NUMBER(v_month_part) BETWEEN 1 AND 12 THEN
RETURN 'Numeric (MM): ' || v_month_part;
ELSE
RETURN 'Invalid numeric month: ' || v_month_part;
END IF;
-- Check for abbreviated month (MON)
ELSIF REGEXP_LIKE(p_date_string, '[A-Z]{3}', 'i') THEN
v_month_part := REGEXP_SUBSTR(p_date_string, '[A-Z]{3}', 1, 1, 'i');
RETURN 'Abbreviated (MON): ' || v_month_part;
-- Check for full month name (MONTH)
ELSIF REGEXP_LIKE(p_date_string, '[A-Z]{4,}', 'i') THEN
v_month_part := REGEXP_SUBSTR(p_date_string, '[A-Z]{4,}', 1, 1, 'i');
RETURN 'Full name (MONTH): ' || v_month_part;
ELSE
RETURN 'Unknown format';
END IF;
END;
/
-- Test with various formats
SELECT
date_string,
detect_month_format(date_string) as month_format
FROM (
SELECT '15-01-2024' as date_string FROM dual UNION ALL
SELECT '15-JAN-2024' FROM dual UNION ALL
SELECT '15-JANUARY-2024' FROM dual UNION ALL
SELECT '15-13-2024' FROM dual UNION ALL
SELECT '15-ABC-2024' FROM dual
);
-- Test month names in different languages
DECLARE
v_date DATE;
v_languages SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST(
'AMERICAN', 'FRENCH', 'SPANISH', 'GERMAN', 'ITALIAN'
);
BEGIN
FOR i IN 1..v_languages.COUNT LOOP
BEGIN
EXECUTE IMMEDIATE 'ALTER SESSION SET NLS_DATE_LANGUAGE = ' || v_languages(i);
v_date := TO_DATE('15-JAN-2024', 'DD-MON-YYYY');
DBMS_OUTPUT.PUT_LINE(v_languages(i) || ': Success with JAN');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(v_languages(i) || ': Failed - ' || SQLERRM);
END;
END LOOP;
-- Reset to default
EXECUTE IMMEDIATE 'ALTER SESSION SET NLS_DATE_LANGUAGE = AMERICAN';
END;
/
-- Find problematic month names
SELECT DISTINCT
date_string,
REGEXP_SUBSTR(date_string, '[A-Z]+', 1, 1, 'i') as month_text,
CASE
WHEN UPPER(REGEXP_SUBSTR(date_string, '[A-Z]+', 1, 1, 'i'))
IN ('JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC')
THEN 'Valid English month'
WHEN UPPER(REGEXP_SUBSTR(date_string, '[A-Z]+', 1, 1, 'i'))
IN ('JANUARY','FEBRUARY','MARCH','APRIL','MAY','JUNE',
'JULY','AUGUST','SEPTEMBER','OCTOBER','NOVEMBER','DECEMBER')
THEN 'Valid full English month'
ELSE 'Invalid or non-English month'
END as month_validation
FROM import_table
WHERE REGEXP_LIKE(date_string, '[A-Z]+', 'i');
-- Fix out-of-range month values
UPDATE import_table
SET date_string = REGEXP_REPLACE(date_string, '-13-', '-12-')
WHERE REGEXP_LIKE(date_string, '-13-');
-- Create function to validate and fix months
CREATE OR REPLACE FUNCTION fix_month_value(
p_date_string VARCHAR2
) RETURN VARCHAR2 AS
v_year VARCHAR2(4);
v_month VARCHAR2(2);
v_day VARCHAR2(2);
v_fixed_month NUMBER;
BEGIN
-- Extract parts (assuming YYYY-MM-DD format)
v_year := SUBSTR(p_date_string, 1, 4);
v_month := SUBSTR(p_date_string, 6, 2);
v_day := SUBSTR(p_date_string, 9, 2);
-- Fix month if invalid
v_fixed_month := TO_NUMBER(v_month);
IF v_fixed_month < 1 THEN
v_fixed_month := 1;
ELSIF v_fixed_month > 12 THEN
v_fixed_month := 12;
END IF;
RETURN v_year || '-' || LPAD(v_fixed_month, 2, '0') || '-' || v_day;
EXCEPTION
WHEN OTHERS THEN
RETURN p_date_string; -- Return original if can't fix
END;
/
-- Apply fixes
UPDATE import_table
SET date_string = fix_month_value(date_string)
WHERE TO_NUMBER(SUBSTR(date_string, 6, 2)) NOT BETWEEN 1 AND 12;
-- Fix common month name errors
CREATE OR REPLACE FUNCTION fix_month_name(
p_date_string VARCHAR2
) RETURN VARCHAR2 AS
v_result VARCHAR2(100);
TYPE month_map_type IS TABLE OF VARCHAR2(10) INDEX BY VARCHAR2(20);
v_month_map month_map_type;
BEGIN
-- Build correction map
v_month_map('JANVIER') := 'JAN';
v_month_map('FEVRIER') := 'FEB';
v_month_map('MARS') := 'MAR';
v_month_map('AVRIL') := 'APR';
v_month_map('MAI') := 'MAY';
v_month_map('JUIN') := 'JUN';
v_month_map('JUILLET') := 'JUL';
v_month_map('AOUT') := 'AUG';
v_month_map('SEPTEMBRE') := 'SEP';
v_month_map('OCTOBRE') := 'OCT';
v_month_map('NOVEMBRE') := 'NOV';
v_month_map('DECEMBRE') := 'DEC';
v_result := UPPER(p_date_string);
-- Replace foreign month names with English
FOR month_name IN v_month_map.FIRST..v_month_map.LAST LOOP
IF v_month_map.EXISTS(month_name) THEN
v_result := REPLACE(v_result, month_name, v_month_map(month_name));
END IF;
END LOOP;
RETURN v_result;
END;
/
-- Convert with language specification
SELECT TO_DATE('15-JANVIER-2024', 'DD-MONTH-YYYY', 'NLS_DATE_LANGUAGE=FRENCH') as french_date
FROM dual;
-- Smart date parser for ambiguous formats
CREATE OR REPLACE FUNCTION parse_ambiguous_date(
p_date_string VARCHAR2,
p_default_format VARCHAR2 DEFAULT 'DD/MM/YYYY'
) RETURN DATE AS
v_parts APEX_T_VARCHAR2;
v_part1 NUMBER;
v_part2 NUMBER;
v_year NUMBER;
v_month NUMBER;
v_day NUMBER;
BEGIN
-- Split date string
v_parts := APEX_STRING.SPLIT(p_date_string, '/');
IF v_parts.COUNT != 3 THEN
-- Try with dash separator
v_parts := APEX_STRING.SPLIT(p_date_string, '-');
END IF;
IF v_parts.COUNT = 3 THEN
v_part1 := TO_NUMBER(v_parts(1));
v_part2 := TO_NUMBER(v_parts(2));
v_year := TO_NUMBER(v_parts(3));
-- Determine if DD/MM or MM/DD
IF v_part1 > 12 AND v_part2 <= 12 THEN
-- First part > 12, must be day
v_day := v_part1;
v_month := v_part2;
ELSIF v_part2 > 12 AND v_part1 <= 12 THEN
-- Second part > 12, must be day
v_month := v_part1;
v_day := v_part2;
ELSE
-- Both could be valid, use default format
IF p_default_format LIKE 'DD%' THEN
v_day := v_part1;
v_month := v_part2;
ELSE
v_month := v_part1;
v_day := v_part2;
END IF;
END IF;
-- Validate and return
IF v_month BETWEEN 1 AND 12 AND v_day BETWEEN 1 AND 31 THEN
RETURN TO_DATE(v_year || '-' || v_month || '-' || v_day, 'YYYY-MM-DD');
END IF;
END IF;
RAISE_APPLICATION_ERROR(-20001, 'Cannot parse date: ' || p_date_string);
END;
/
-- Test ambiguous dates
SELECT
date_string,
parse_ambiguous_date(date_string, 'DD/MM/YYYY') as dd_mm_result,
parse_ambiguous_date(date_string, 'MM/DD/YYYY') as mm_dd_result
FROM (
SELECT '13/01/2024' as date_string FROM dual UNION ALL -- Unambiguous: day=13
SELECT '01/13/2024' FROM dual UNION ALL -- Unambiguous: month=13 (invalid)
SELECT '05/06/2024' FROM dual -- Ambiguous
);
-- Set session NLS parameters
ALTER SESSION SET NLS_DATE_LANGUAGE = 'AMERICAN';
ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YYYY';
-- Convert with explicit NLS settings
SELECT
TO_DATE('15-JAN-2024', 'DD-MON-YYYY', 'NLS_DATE_LANGUAGE=AMERICAN') as english_date,
TO_DATE('15-ENE-2024', 'DD-MON-YYYY', 'NLS_DATE_LANGUAGE=SPANISH') as spanish_date,
TO_DATE('15-JAN-2024', 'DD-MON-YYYY', 'NLS_DATE_LANGUAGE=FRENCH') as french_date
FROM dual;
-- Create conversion function with language detection
CREATE OR REPLACE FUNCTION convert_multilingual_date(
p_date_string VARCHAR2
) RETURN DATE AS
v_date DATE;
TYPE lang_array IS TABLE OF VARCHAR2(30);
v_languages lang_array := lang_array(
'AMERICAN', 'SPANISH', 'FRENCH', 'GERMAN', 'ITALIAN', 'PORTUGUESE'
);
BEGIN
-- Try each language
FOR i IN 1..v_languages.COUNT LOOP
BEGIN
v_date := TO_DATE(p_date_string, 'DD-MON-YYYY',
'NLS_DATE_LANGUAGE=' || v_languages(i));
RETURN v_date;
EXCEPTION
WHEN OTHERS THEN
NULL; -- Try next language
END;
END LOOP;
-- If no language worked, raise error
RAISE_APPLICATION_ERROR(-20001,
'Cannot convert date in any known language: ' || p_date_string);
END;
/
CREATE OR REPLACE PACKAGE date_parser AS
-- Month name mappings
TYPE month_map_type IS TABLE OF VARCHAR2(10) INDEX BY VARCHAR2(30);
FUNCTION parse_date(
p_date_string VARCHAR2,
p_format_hint VARCHAR2 DEFAULT NULL
) RETURN DATE;
FUNCTION normalize_month(p_month_string VARCHAR2) RETURN VARCHAR2;
FUNCTION validate_month(p_month VARCHAR2) RETURN BOOLEAN;
PROCEDURE register_month_alias(
p_alias VARCHAR2,
p_standard VARCHAR2
);
END date_parser;
/
CREATE OR REPLACE PACKAGE BODY date_parser AS
g_month_aliases month_map_type;
-- Initialize month aliases
PROCEDURE init_month_aliases AS
BEGIN
-- English variations
g_month_aliases('JANUARY') := '01';
g_month_aliases('FEBRUARY') := '02';
g_month_aliases('MARCH') := '03';
g_month_aliases('APRIL') := '04';
g_month_aliases('MAY') := '05';
g_month_aliases('JUNE') := '06';
g_month_aliases('JULY') := '07';
g_month_aliases('AUGUST') := '08';
g_month_aliases('SEPTEMBER') := '09';
g_month_aliases('OCTOBER') := '10';
g_month_aliases('NOVEMBER') := '11';
g_month_aliases('DECEMBER') := '12';
-- Abbreviations
g_month_aliases('JAN') := '01';
g_month_aliases('FEB') := '02';
g_month_aliases('MAR') := '03';
g_month_aliases('APR') := '04';
g_month_aliases('JUN') := '06';
g_month_aliases('JUL') := '07';
g_month_aliases('AUG') := '08';
g_month_aliases('SEP') := '09';
g_month_aliases('OCT') := '10';
g_month_aliases('NOV') := '11';
g_month_aliases('DEC') := '12';
-- Spanish
g_month_aliases('ENERO') := '01';
g_month_aliases('FEBRERO') := '02';
g_month_aliases('MARZO') := '03';
g_month_aliases('ABRIL') := '04';
g_month_aliases('MAYO') := '05';
g_month_aliases('JUNIO') := '06';
g_month_aliases('JULIO') := '07';
g_month_aliases('AGOSTO') := '08';
g_month_aliases('SEPTIEMBRE') := '09';
g_month_aliases('OCTUBRE') := '10';
g_month_aliases('NOVIEMBRE') := '11';
g_month_aliases('DICIEMBRE') := '12';
END init_month_aliases;
FUNCTION normalize_month(p_month_string VARCHAR2) RETURN VARCHAR2 AS
v_upper_month VARCHAR2(30);
BEGIN
v_upper_month := UPPER(TRIM(p_month_string));
-- Check if it's already numeric
IF REGEXP_LIKE(v_upper_month, '^\d{1,2}$') THEN
RETURN LPAD(v_upper_month, 2, '0');
END IF;
-- Look up in aliases
IF g_month_aliases.COUNT = 0 THEN
init_month_aliases;
END IF;
IF g_month_aliases.EXISTS(v_upper_month) THEN
RETURN g_month_aliases(v_upper_month);
END IF;
-- Return as-is if not found
RETURN p_month_string;
END normalize_month;
FUNCTION validate_month(p_month VARCHAR2) RETURN BOOLEAN AS
v_month_num NUMBER;
BEGIN
v_month_num := TO_NUMBER(normalize_month(p_month));
RETURN v_month_num BETWEEN 1 AND 12;
EXCEPTION
WHEN OTHERS THEN
RETURN FALSE;
END validate_month;
FUNCTION parse_date(
p_date_string VARCHAR2,
p_format_hint VARCHAR2 DEFAULT NULL
) RETURN DATE AS
v_normalized VARCHAR2(100);
v_parts APEX_T_VARCHAR2;
v_year VARCHAR2(4);
v_month VARCHAR2(2);
v_day VARCHAR2(2);
BEGIN
-- Normalize the date string
v_normalized := TRIM(p_date_string);
-- Try to extract parts
IF REGEXP_LIKE(v_normalized, '\d{4}') THEN
v_year := REGEXP_SUBSTR(v_normalized, '\d{4}');
END IF;
-- Extract month (could be text or number)
IF REGEXP_LIKE(v_normalized, '[A-Z]+', 'i') THEN
v_month := normalize_month(REGEXP_SUBSTR(v_normalized, '[A-Z]+', 1, 1, 'i'));
ELSIF REGEXP_LIKE(v_normalized, '\d{1,2}[-/]\d{1,2}') THEN
v_parts := APEX_STRING.SPLIT(REGEXP_SUBSTR(v_normalized, '\d{1,2}[-/]\d{1,2}'), '[-/]');
-- Determine which is month based on value
IF TO_NUMBER(v_parts(1)) > 12 THEN
v_month := LPAD(v_parts(2), 2, '0');
v_day := LPAD(v_parts(1), 2, '0');
ELSE
v_month := LPAD(v_parts(1), 2, '0');
v_day := LPAD(v_parts(2), 2, '0');
END IF;
END IF;
-- Build and return date
IF v_year IS NOT NULL AND v_month IS NOT NULL AND v_day IS NOT NULL THEN
RETURN TO_DATE(v_year || '-' || v_month || '-' || v_day, 'YYYY-MM-DD');
END IF;
RAISE_APPLICATION_ERROR(-20001, 'Cannot parse date: ' || p_date_string);
END parse_date;
PROCEDURE register_month_alias(
p_alias VARCHAR2,
p_standard VARCHAR2
) AS
BEGIN
g_month_aliases(UPPER(p_alias)) := p_standard;
END register_month_alias;
BEGIN
-- Initialize on package load
init_month_aliases;
END date_parser;
/
-- Create month validation rules
CREATE TABLE month_validation_rules (
rule_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
rule_name VARCHAR2(100),
pattern VARCHAR2(200),
valid_values CLOB,
error_message VARCHAR2(500),
active CHAR(1) DEFAULT 'Y'
);
-- Insert validation rules
INSERT INTO month_validation_rules (rule_name, pattern, valid_values, error_message)
VALUES ('Numeric Month', '^\d{2}$', '01,02,03,04,05,06,07,08,09,10,11,12',
'Month must be between 01 and 12');
INSERT INTO month_validation_rules (rule_name, pattern, valid_values, error_message)
VALUES ('English Abbrev', '^[A-Z]{3}$', 'JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC',
'Invalid month abbreviation');
-- Validation procedure
CREATE OR REPLACE PROCEDURE validate_date_months(
p_table_name VARCHAR2,
p_column_name VARCHAR2
) AS
v_invalid_count NUMBER;
v_sql VARCHAR2(4000);
BEGIN
-- Check for invalid numeric months
v_sql := 'SELECT COUNT(*) FROM ' || p_table_name ||
' WHERE REGEXP_LIKE(' || p_column_name || ', ''\d{4}-\d{2}-\d{2}'')' ||
' AND TO_NUMBER(SUBSTR(' || p_column_name || ', 6, 2)) NOT BETWEEN 1 AND 12';
EXECUTE IMMEDIATE v_sql INTO v_invalid_count;
IF v_invalid_count > 0 THEN
DBMS_OUTPUT.PUT_LINE('Found ' || v_invalid_count || ' records with invalid months');
-- Log details
INSERT INTO data_quality_issues (
table_name, column_name, issue_type,
record_count, discovered_date
) VALUES (
p_table_name, p_column_name, 'Invalid Month',
v_invalid_count, SYSDATE
);
END IF;
END;
/
-- Add check constraint for valid months
ALTER TABLE date_table
ADD CONSTRAINT chk_valid_month
CHECK (
TO_NUMBER(SUBSTR(date_column, 6, 2)) BETWEEN 1 AND 12
);
-- Create validation trigger
CREATE OR REPLACE TRIGGER validate_month_before_insert
BEFORE INSERT OR UPDATE ON import_table
FOR EACH ROW
DECLARE
v_month NUMBER;
BEGIN
IF :NEW.date_string IS NOT NULL THEN
-- Extract and validate month
v_month := TO_NUMBER(SUBSTR(:NEW.date_string, 6, 2));
IF v_month NOT BETWEEN 1 AND 12 THEN
RAISE_APPLICATION_ERROR(-20001,
'Invalid month value: ' || v_month || '. Must be between 1 and 12.');
END IF;
END IF;
END;
/
-- Create function to standardize date input
CREATE OR REPLACE FUNCTION standardize_date_input(
p_date_string VARCHAR2,
p_expected_format VARCHAR2 DEFAULT 'YYYY-MM-DD'
) RETURN VARCHAR2 AS
v_result VARCHAR2(30);
BEGIN
-- Remove common issues
v_result := TRIM(UPPER(p_date_string));
-- Fix common month errors
v_result := REPLACE(v_result, 'JANAURY', 'JANUARY');
v_result := REPLACE(v_result, 'FEBUARY', 'FEBRUARY');
-- Convert to standard format
IF p_expected_format = 'YYYY-MM-DD' THEN
-- Ensure numeric month
v_result := REGEXP_REPLACE(v_result, 'JAN', '01');
v_result := REGEXP_REPLACE(v_result, 'FEB', '02');
-- ... etc for all months
END IF;
RETURN v_result;
END;
/
  • ORA-01830 - Date format picture ends before converting
  • ORA-01847 - Day of month must be between 1 and last day
  • ORA-01858 - Non-numeric character where numeric expected
  • ORA-01861 - Literal does not match format string
  1. ✓ Check month values are between 1-12
  2. ✓ Verify month name spelling and case
  3. ✓ Check NLS_DATE_LANGUAGE setting
  4. ✓ Confirm date format (DD/MM vs MM/DD)
  5. ✓ Fix or standardize month values
  6. ✓ Use appropriate format mask
-- Check for invalid months
SELECT * FROM table WHERE TO_NUMBER(SUBSTR(date_col, 6, 2)) NOT BETWEEN 1 AND 12;
-- Convert with language
SELECT TO_DATE('15-JAN-2024', 'DD-MON-YYYY', 'NLS_DATE_LANGUAGE=AMERICAN') FROM dual;
-- Fix month value
UPDATE table SET date_col = REPLACE(date_col, '-13-', '-12-') WHERE date_col LIKE '%-13-%';
-- Check NLS settings
SELECT * FROM nls_session_parameters WHERE parameter LIKE '%DATE%';
-- Test month recognition
SELECT TO_DATE('15-' || month_name || '-2024', 'DD-MON-YYYY') FROM month_list;
  • Validate month ranges - Ensure 1-12 for numeric months
  • Standardize formats - Use consistent date formats
  • Handle languages - Set appropriate NLS_DATE_LANGUAGE
  • Check spellings - Validate month name spelling
  • Document formats - Clearly specify expected formats
  • Test thoroughly - Include edge cases in testing