5

Given a string representation of a day of the week (i.e. Monday, Mardi, الثلاثاء etc.) and a NLS_DATE_LANGUAGE is there any way of validating that this Day of the Week is valid?

Why is this difficult you ask? Well, if this were just English the obvious thing to do would be something like the following:

if <day_variable> in ('Monday','Tuesday', ...) then
   ...

I'm trying to do this, extensibly, for several countries and as I don't know (and can't be bothered) to write out all the days of the week for all current and future NLS_DATE_LANGUAGE's this isn't really an option.

Next option is an explicit TO_DATE(), which works beautifully with Friday - I suspect that this is a coincidence 'cause it's May, but fails miserably with the rest of the week:

SQL> select to_date('Friday', 'Day') as d from dual;

D
----------
2015-05-01

SQL> select to_date('Monday', 'Day') as d from dual;
select to_date('Monday', 'Day') as d from dual
               *
ERROR at line 1:
ORA-01835: day of week conflicts with Julian date

That's an interesting error... Oracle recommends to:

Remove the day of the week value from the date specification or enter the correct day of the week for the Julian date.

Removing the day of the week isn't really an option as that's all I'm interested and I don't have an incorrect day of the week for my Julian date because I don't have a Julian date.

Workaround's suggested elsewhere confirm and using alternative date formats (Dy, dy, FMDy, etc.) fairly obviously results in the same error.

It appears as though Oracle represents all dates internally as Julian dates and is getting confused in this situation.

How can I validate that a named day is correct in Oracle?

Ben
  • 51,770
  • 36
  • 127
  • 149
  • P.S. I've self-answered this but that by no-means implies that this is the best answer. I'm looking for something better (i.e. inbuilt and only in SQL) if at all possible. – Ben May 05 '15 at 08:35

3 Answers3

2

As far as I can tell I'm stuck with running something like the following every time we patch the DB (certainly won't remember to do that) to generate all possible days

select value as language
     , to_char(sysdate + l, 'FMDay', 'nls_date_language=''' || value || '') as name_of_day
  from v$nls_valid_values
 cross join ( select level as l from dual connect by level <= 7 )
 where parameter = 'LANGUAGE'
   and isdeprecated = 'FALSE'
 order by language

Doing this would not be at all extensible as if I were to need the day of the week (i.e. 1, 2, 3, etc.) the value is based on the current NLS_TERRITORY, which means there's no possible consistency.

to_char(sysdate + l, 'D', 'nls_date_language=''' || value || '') as day_of_week

I'd then have to create a non-deterministic, :-(, function to validate whether the string is (in)correct:

create or replace function is_day_of_week (
    PDay in varchar2
  , PDate_Language
    ) return number is

   l_ct number;

begin

   select count(*) into l_ct
     from days_of_the_week
    where name_of_day = PDay
      and language = PDate_Language;

   return l_ct;

end is_day_of_week;
Ben
  • 51,770
  • 36
  • 127
  • 149
2

Observe that a string representation of any 7 consecutive calendar dates that contains the day of the week must have the tested day-of-week string as a substring iff the tested string is valid.

Thus to circumvent the error, concatenate the string representations of the week starting with sysdate and test your string against the result:

SELECT CASE INSTR(
               to_char ( sysdate + 0, 'Day, DD.MM.YYYY' )   
            || to_char ( sysdate + 1, 'Day, DD.MM.YYYY' )
            || to_char ( sysdate + 2, 'Day, DD.MM.YYYY' )
            || to_char ( sysdate + 3, 'Day, DD.MM.YYYY' )
            || to_char ( sysdate + 4, 'Day, DD.MM.YYYY' )
            || to_char ( sysdate + 5, 'Day, DD.MM.YYYY' )
            || to_char ( sysdate + 6, 'Day, DD.MM.YYYY' )
          , '<the_string_to_test>' )
         WHEN 0 THEN 'invalid'
         ELSE        'valid'
       END  isvalid
  FROM DUAL
     ;

As a refinement, delimiters around the day part if the format strings and the tested string can guard against false positives where the tested string is a substring of a valid day name.

SELECT CASE INSTR(
               to_char ( sysdate + 0, '.FMDay., DD.MM.YYYY' )   
            || to_char ( sysdate + 1, '.FMDay., DD.MM.YYYY' )
            || to_char ( sysdate + 2, '.FMDay., DD.MM.YYYY' )
            || to_char ( sysdate + 3, '.FMDay., DD.MM.YYYY' )
            || to_char ( sysdate + 4, '.FMDay., DD.MM.YYYY' )
            || to_char ( sysdate + 5, '.FMDay., DD.MM.YYYY' )
            || to_char ( sysdate + 6, '.FMDay., DD.MM.YYYY' )
          , '.' || '<the_string_to_test>' || '.')
         WHEN 0 THEN 'invalid'
         ELSE        'valid'
       END  isvalid
  FROM DUAL
     ;

Edit/Note

In the latter example note that ...

  • Not all delimiters work ( tested with . [works] and | [fails] )
  • FMDay must be used as format specifier to trim trailing whitespace from the day-of-week names

Edit #2

Here is the same (almost) without the need of string operations:

select 
  case when upper(:day_name) in 
  (
    select to_char(sysdate + 0, 'FMDAY', 'NLS_DATE_LANGUAGE=''' || :nls_date_lang || '''') from dual union all
    select to_char(sysdate + 1, 'FMDAY', 'NLS_DATE_LANGUAGE=''' || :nls_date_lang || '''') from dual union all
    select to_char(sysdate + 2, 'FMDAY', 'NLS_DATE_LANGUAGE=''' || :nls_date_lang || '''') from dual union all
    select to_char(sysdate + 3, 'FMDAY', 'NLS_DATE_LANGUAGE=''' || :nls_date_lang || '''') from dual union all
    select to_char(sysdate + 4, 'FMDAY', 'NLS_DATE_LANGUAGE=''' || :nls_date_lang || '''') from dual union all
    select to_char(sysdate + 5, 'FMDAY', 'NLS_DATE_LANGUAGE=''' || :nls_date_lang || '''') from dual union all
    select to_char(sysdate + 6, 'FMDAY', 'NLS_DATE_LANGUAGE=''' || :nls_date_lang || '''') from dual
  ) then 'valid' else 'invalid'
  end  isvalid
from dual;
collapsar
  • 17,010
  • 4
  • 35
  • 61
  • This is a great and simple solution. It would fail on German 'Sonnabend' as shown in my answer, but is a great solution nonetheless. I'll take the freedom to edit your answer and add an alternative which is based on your query, so making that a separate answer would not be justifiable. – Thorsten Kettner May 05 '15 at 10:38
  • The issue is with this is that there are no guarantees that one languages day of the week is not wholly contained within another. For instance the Indonesian Jumat and the Uzbek Juma are substrings of the Swahili Jumatano. @Thorsten's edit solves this, but would still be a horrible query :-(. – Ben May 05 '15 at 10:41
  • @Ben This substring problem is handled by my second version (unless `.` is part of a valid workday name which will not happen). Wrt to your edit: have you profiled and tested your statement ? I couldn't make my version work through sql plus using the `FMDAY`specifier only and the union of subqueries might lose performance. – collapsar May 05 '15 at 10:49
  • @Ben Must have missed something, your version basically works. – collapsar May 05 '15 at 10:51
  • I didn't edit it... but a 7 row sub-query will not effect performance at all – Ben May 05 '15 at 10:53
  • @Ben Ok, sorry for mistakenly attributing that to you. – collapsar May 05 '15 at 10:59
  • @Ben You're right wrt the performance, the impact is below 5% (data collected using a simple test script using dbms_utility.get_time on 100000 iterations for each variant). – collapsar May 05 '15 at 11:11
  • @collapsar: I don't understand why you've inserted string operations in the last query. The charm about the query was they ain't needed. What do you think you gain by looking up '.SATURDAY.' instead of 'SATURDAY' in an array? – Thorsten Kettner May 05 '15 at 11:47
  • @ThorstenKettner Because I am braindead ... ;-). I was still thinking along the lines of the string based solution. Edit revoked. – collapsar May 05 '15 at 12:08
0

The short answer is: You cannot do this with Oracle.

You can write a PL/SQL function as follows, using a loop with NEXT DAY:

create or replace function is_valid_dayname(
  vi_dayname varchar2, 
  vi_nls_date_language varchar2) return integer 
as
  -- Oracle day name constants for NEXT_DAY
  type type_daynames is varray(7) of varchar2(100);
  v_daynames type_daynames := type_daynames('SUNDAY','MONDAY','TUESDAY','WEDNESDAY','THURSDAY','FRIDAY','SATURDAY');
  -- date variable for the INTO clause
  v_date date;
begin
  for i in 1 .. 7 loop
    begin 
      select to_date(vi_dayname || to_char(next_day(sysdate, v_daynames(i)), 'yyyymmdd', 'NLS_DATE_LANGUAGE=''' || vi_nls_date_language || ''''), 'day yyyymmdd') into v_date from dual;
      return 1; -- success
    exception when others then null;
    end;
  end loop;
  return 0; -- failure
end;

and use it so:

select is_valid_dayname('invalid dayname', 'GERMAN') from dual;

0

select is_valid_dayname('samstag', 'GERMAN') from dual;

1

select is_valid_dayname('SAMSTAG', 'GERMAN') from dual;

1

select is_valid_dayname('Samstag', 'GERMAN') from dual;

1

select is_valid_dayname('Sonnabend', 'GERMAN') from dual;

0

Oops, 'Sonnabend' is a synonym for 'Samstag' in German, but Oracle doesn't know this. So we get close to a solution, but it isn't complete.

Thorsten Kettner
  • 89,309
  • 7
  • 49
  • 73
  • I don't think it is a good idea to consider `Sonnabend` as a valid day. Weekdays are defined by ISO 8601 (and other specification bodies) and there the 6th day of the week is "Samstag", nothing else. – Wernfried Domscheit May 05 '15 at 11:10
  • @Wernfried: Do they use "Sonnabend" in Switzerland? In Germany (at least in the part where I live), Sonnabend and Samstag are both considered valid and none is better than the other (though Sonnabend seems to go out of fashion). A person entering "Sonnabend" and being asked to enter a valid day name instead would be quite surprised here. It would also be strange to require of a user to know the ISO names. But well, maybe no user input is involved here, then yes, we could limit this to ISO names. – Thorsten Kettner May 05 '15 at 11:21
  • In Switzerland we say "Samschtig" (or even worse "Zischtig" for Tuesday:-) – Wernfried Domscheit May 05 '15 at 11:39