1 Introduction
2 Ground Rules

Building a File System
3 File Systems
4 File Content Data Structure
5 Allocation Cluster Manager
6 Exceptions and Emancipation
7 Base Classes, Testing, and More
8 File Meta Data
9 Native File Class
10 Our File System
11 Allocation Table
12 File System Support Code
13 Initializing the File System
14 Contiguous Files
15 Rebuilding the File System
16 Native File System Support Methods
17 Lookups, Wildcards, and Unicode, Oh My
18 Finishing the File System Class

The Init Program
19 Hardware Abstraction and UOS Architecture
20 Init Command Mode
21 Using Our File System
22 Hardware and Device Lists
23 Fun with Stores: Partitions
24 Fun with Stores: RAID
25 Fun with Stores: RAM Disks
26 Init wrap-up

The Executive
27 Overview of The Executive
28 Starting the Kernel
29 The Kernel
30 Making a Store Bootable
31 The MMC
32 The HMC
33 Loading the components
34 Using the File Processor
35 Symbols and the SSC
36 The File Processor and Device Management
37 The File Processor and File System Management
38 Finishing Executive Startup

Users and Security
39 Introduction to Users and Security
40 More Fun With Stores: File Heaps
41 File Heaps, part 2
42 SysUAF
43 TUser
44 SysUAF API

Terminal I/O
45 Shells and UCL
46 UOS API, the Application Side
47 UOS API, the Executive Side
48 I/O Devices
49 Streams
50 Terminal Output Filters
51 The TTerminal Class
52 Handles
53 Putting it All Together
54 Getting Terminal Input
55 QIO
56 Cooking Terminal Input
57 Putting it all together, part 2
58 Quotas and I/O

UCL
59 UCL Basics
60 Symbol Substitution
61 UCL Command execution
62 UCL Command execution, part 2
63 UCL Command Abbreviation
64 ASTs
65 UCL Expressions, Part 1
66 UCL Expressions, Part 2: Support code
67 UCL Expressions, part 3: Parsing
68 SYS_GETJPIW and SYS_TRNLNM
69 UCL Expressions, part 4: Evaluation

UCL Lexical Functions
70 PROCESS_SCAN
71 PROCESS_SCAN, Part 2
72 TProcess updates
73 Unicode revisted
74 Lexical functions: F$CONTEXT
75 Lexical functions: F$PID
76 Lexical Functions: F$CUNITS
77 Lexical Functions: F$CVSI and F$CVUI
78 UOS Date and Time Formatting
79 Lexical Functions: F$CVTIME
80 LIB_CVTIME
81 Date/Time Contexts
82 SYS_GETTIM, LIB_Get_Timestamp, SYS_ASCTIM, and LIB_SYS_ASCTIM
83 Lexical Functions: F$DELTA_TIME
84 Lexical functions: F$DEVICE
85 SYS_DEVICE_SCAN
86 Lexical functions: F$DIRECTORY
87 Lexical functions: F$EDIT and F$ELEMENT
88 Lexical functions: F$ENVIRONMENT
89 SYS_GETUAI
90 Lexical functions: F$EXTRACT and F$IDENTIFIER
91 LIB_FAO and LIB_FAOL
92 LIB_FAO and LIB_FAOL, part 2
93 Lexical functions: F$FAO
94 File Processing Structures
95 Lexical functions: F$FILE_ATTRIBUTES
96 SYS_DISPLAY
97 UCL Lexical functions: F$GETDVI
98 Parse_GetDVI

Glossary/Index


Download sources
Download binaries

Date/Time Contexts

In the previous article, we covered the LIB_CVTIME function in Starlet, and its support code. In this article, we will look at Date/Time Contexts (TDate_Time_Context), which were used extensively in the last article.

The point of TDate_Time_Context is to provide an interface to the localization of language, dates, and time.

const DTFS_MD0 = 0 ;
const DTFS_MDD = 1 ;
const DTFS_MDB = 2 ;
const DTFS_MWU = 3 ;
const DTFS_MWAU = 4 ;
const DTFS_MWC = 5 ;
const DTFS_MWAC = 6 ;
const DTFS_MWL = 7 ;
const DTFS_MWAL = 8 ;
const DTFS_MMAU = 9 ;
const DTFS_MMAAU = 10 ;
const DTFS_MMAC = 11 ;
const DTFS_MMAAC = 12 ;
const DTFS_MMAL = 13 ;
const DTFS_MMAAL = 14 ;
const DTFS_MMN0 = 15 ;
const DTFS_MMNM = 16 ;
const DTFS_MMNB = 17 ;
const DTFS_MY4 = 18 ;
const DTFS_MY3 = 19 ;
const DTFS_MY2 = 20 ;
const DTFS_MY1 = 21 ;
const DTFS_MZ4 = 22 ;
const DTFS_MZ3 = 23 ;
const DTFS_MZ2 = 24 ;
const DTFS_MZ1 = 25 ;
const DTFS_MH04 = 26 ;
const DTFS_MHH4 = 27 ;
const DTFS_MHB4 = 28 ;
const DTFS_MH02 = 29 ;
const DTFS_MHH2 = 30 ;
const DTFS_MHB2 = 31 ;
const DTFS_MM0 = 32 ;
const DTFS_MMM = 33 ;
const DTFS_MMB = 34 ;
const DTFS_MS0 = 35 ;
const DTFS_MSS = 36 ;
const DTFS_MS = 37 ;
const DTFS_MC7 = 38 ;
const DTFS_MC6 = 39 ;
const DTFS_MC5 = 40 ;
const DTFS_MC4 = 41 ;
const DTFS_MC3 = 42 ;
const DTFS_MC2 = 43 ;
const DTFS_MC1 = 44 ;
const DTFS_MMIU = 45 ;
const DTFS_MMIC = 46 ;
const DTFS_MIL = 47 ;

const Format_Specifiers : array[ DTFS_MD0..DTFS_MIL ] of string = (
                                                      '!D0',
                                                      '!DD',
                                                      '!DB',
                                                      '!WU',
                                                      '!WAU',
                                                      '!WC',
                                                      '!WAC',
                                                      '!WL',
                                                      '!WAL',
                                                      '!MAU',
                                                      '!MAAU',
                                                      '!MAC',
                                                      '!MAAC',
                                                      '!MAL',
                                                      '!MAAL',
                                                      '!MN0',
                                                      '!MNM',
                                                      '!MNB',
                                                      '!Y4',
                                                      '!Y3',
                                                      '!Y2',
                                                      '!Y1',
                                                      '!Z4',
                                                      '!Z3',
                                                      '!Z2',
                                                      '!Z1',
                                                      '!H04',
                                                      '!HH4',
                                                      '!HB4',
                                                      '!H02',
                                                      '!HH2',
                                                      '!HB2',
                                                      '!M0',
                                                      '!MM',
                                                      '!MB',
                                                      '!S0',
                                                      '!SS',
                                                      '!SB',
                                                      '!C7',
                                                      '!C6',
                                                      '!C5',
                                                      '!C4',
                                                      '!C3',
                                                      '!C2',
                                                      '!C1',
                                                      '!MIU',
                                                      '!MIC',
                                                      '!MIL'
                                                    ) ;
These constants are used to process the date and time codes, described in article 78

const LIB_K_MONTH_NAME_U = 0 ;
const LIB_K_MONTH_NAME_L = 1 ;
const LIB_K_MONTH_NAME_C = 2 ;
const LIB_K_MONTH_NAME_ABB_U = 3 ;
const LIB_K_MONTH_NAME_ABB_L = 4 ;
const LIB_K_MONTH_NAME_ABB_C = 5 ;
const LIB_K_FORMAT_MNEMONICS = 6 ;
const LIB_K_WEEKDAY_NAME_U = 7 ;
const LIB_K_WEEKDAY_NAME_L = 8 ;
const LIB_K_WEEKDAY_NAME_C = 9 ;
const LIB_K_WEEKDAY_NAME_ABB_U = 10 ;
const LIB_K_WEEKDAY_NAME_ABB_L = 11 ;
const LIB_K_WEEKDAY_NAME_ABB_C = 12 ;
const LIB_K_RELATIVE_DAY_NAME_U = 13 ;
const LIB_K_RELATIVE_DAY_NAME_L = 14 ;
const LIB_K_RELATIVE_DAY_NAME_C = 15 ;
const LIB_K_MERIDIEM_INDICATOR_U = 16 ;
const LIB_K_MERIDIEM_INDICATOR_L = 17 ;
const LIB_K_MERIDIEM_INDICATOR_C = 18 ;
const LIB_K_OUTPUT_FORMAT = 19 ;
const LIB_K_INPUT_FORMAT = 20 ;
const LIB_K_LANGUAGE = 21 ;
These constants are used to indicate the various date and time components.

type TDate_Time_Context = class
                              public // Constructors and destructors...
                                  constructor Create ;
                                  destructor Destroy ; override ;

                              private // Instance data...
                                  _Components : array[ 0..LIB_K_LANGUAGE ] of string ;
                                  _Parsed_Lists : array[ 0..LIB_K_LANGUAGE - 1 ] of TStringList ;
                                  _Parsed_Output_Date_Time_List : TStringList ;
                                  _Parsed_Output_Date_List : TStringList ;
                                  _Parsed_Output_Time_List : TStringList ;
                                  Defaulted_To_English : boolean ;

                              protected // Property handlers...
                                  function Get_Component( Index : integer ) : string ;
                                  procedure Set_Component( Index : integer ;
                                      Value : string ) ;

                              protected // Internal utility routines...
                                  procedure _Parse_List( Index : integer ) ;
                                  procedure Parse_Inputs ;
                                  procedure Parse_String( SL : TStringList ;
                                      S : string ) ;
                                  function Parameter_Counts( Component : integer ;
                                      const Value : string ) : boolean ;

                              public // API...
                                  procedure Load_Language_Date_Time_Formats( Language : string ) ;
                                  function Parsed_List( Index : integer ) : TStringList ;
                                  function Parsed_Output_Date_Time_List : TStringList ;
                                  function Parsed_Output_Date_List : TStringList ;
                                  function Parsed_Output_Time_List : TStringList ;
                                  function Index( Component : integer ; S : string ;
                                      St : integer ; var En : integer ) : integer ;

                              public // Properties...
                                  property Component[ Index : integer ] : string
                                      read Get_Component
                                      write Set_Component ;
                          end ; // TDate_Time_Context
This is the class definition for TDate_Time_Context.

// TDate_Time_Context methods...

// Constructors and destructors...

constructor TDate_Time_Context.Create ;

var I : integer ;

begin
    inherited Create ;

    for I := 0 to high( _Parsed_Lists ) do
    begin
        _Parsed_Lists[ I ] := TStringList.Create ;
    end ;
    Load_Language_Date_Time_Formats( '' ) ;
end ;


destructor TDate_Time_Context.Destroy ;

var I : integer ;

begin
    for I := 0 to high( _Parsed_Lists ) do
    begin
        _Parsed_Lists[ I ].Free ;
    end ;

    inherited Destroy ;
end ;
The constructor creates the various string lists and loads the default language and localized date/time values. The destructor simply deletes the string lists.

// Property handlers...

function TDate_Time_Context.Get_Component( Index : integer ) : string ;

begin
    Result := _Components[ Index ] ;
end ;


procedure TDate_Time_Context.Set_Component( Index : integer ; Value : string ) ;

begin
    _Components[ Index ] := Value ;
    if( Index = LIB_K_LANGUAGE ) then
    begin
        Load_Language_Date_Time_Formats( Value ) ;
    end ;
    if( Index <= high( _Parsed_Lists ) ) then
    begin
        _Parsed_Lists[ Index ].Clear ;
    end ;
end ;
These are the property handlers for the Components property. The getter returns the specified string list. The setter assigns the specified string list. If the index is the language (LIB_K_LANGAUGE), we load the default date and time formats associated with that language. If the index is in range of the parsed lists, we clear the corresponding list. We must check this because there isn't a parsed list for each component (for instance, the language has no corresponding parsed list).

procedure TDate_Time_Context.Parse_Inputs ;

begin
    Parse_String( _Parsed_Lists[ LIB_K_INPUT_FORMAT ], _Components[ LIB_K_INPUT_FORMAT ] ) ;
end ;


procedure TDate_Time_Context.Parse_String( SL : TStringList ; S : string ) ;

var I : integer ;
    T : string ;

begin
    // Setup...
    S := trim( S ) ;
    SL.Clear ;

    // Parse the format string...
    while( S <> '' ) do
    begin
        I := pos( '!', S + '!' ) ;
        if( I > 1 ) then
        begin
            SL.Add( copy( S, 1, I - 1 ) ) ;
            S := copy( S, I, length( S ) ) ;
        end ;
        if( S = '' ) then
        begin
            break ;
        end ;
        for I := 0 to high( Format_Specifiers ) do
        begin
            T := Format_Specifiers[ I ] ;
            if( copy( S, 1, length( T ) ) = T ) then
            begin
                SL.Add( copy( S, 1, length( T ) ) ) ; // Add item to parsed list
                S := copy( S, length( T ) + 1, length( S ) ) ; // Trim from string
                break ;
            end ;
        end ; // for I := 0 to high( Format_Specifiers )
    end ; // while( S <> '' )
end ; // TDate_Time_Context.Parse_String
Parse_Inputs is used to parse the input formats. It simply calls Parse_String to parse the input format component.

procedure TDate_Time_Context._Parse_List( Index : integer ) ;

var C : char ;
    P : integer ;
    S, T : string ;

begin
    if( Index = LIB_K_INPUT_FORMAT ) then
    begin
        Parse_Inputs ;
    end else
    begin
        _Parsed_Lists[ Index ].Clear ;
        S := _Components[ Index ] ;
        if( length( S ) < 3 ) then
        begin
            exit ;
        end ;
        C := S[ 1 ] ;
        while( S <> '' ) do
        begin
            S := copy( S, 2, length( S ) ) ; // Strip first delimiter
            P := pos( C, S ) ;
            if( P = 0 ) then // No more delimiters - end of list
            begin
                exit ;
            end ;
            T := copy( S, 1, P - 1 ) ;
            S := copy( S, P, length( S ) ) ;
            _Parsed_Lists[ Index ].Add( T ) ;
        end ;
    end ;
end ; // TDate_Time_Context._Parse_List
This method builds a parsed list for a given component. In the case of LIB_K_INPUT_FORMAT, we call Parse_Inputs. Otherwise, we clear any existing parsed list for this index, and check for a null string. If null, our work is done and we exit. Otherwise, we take the first character of the string and use that as a delimiter. We iterate through the string, looking for the next delimiter, extracting the text between delimiters to the list. Thus, a string like:
|A|B|C|
would be parsed into three items: "A", "B", and "C".

function Get_Format( Table, Symbol, Value, Default : string ) : string ;

begin
    if( Value <> '' ) then
    begin
        Result := Value ;
        exit ;
    end ;
    Result := TRNLNM( 'LNM$' + Table, Symbol ) ;
    if( Result = '' ) then
    begin
        Result := Default ;
    end ;
end ;
This function returns Value if it isn't null. Otherwise, the funtion returns the value of a symbol within a given symbol table. If the symbol is null or doesn't exist, it returns the passed default.

function TDate_Time_Context.Parsed_Output_Date_Time_List : TStringList ;

begin
    if( _Parsed_Output_Date_Time_List = nil ) then
    begin
        _Parsed_Output_Date_Time_List := TStringList.Create ;
        Parse_String( _Parsed_Output_Date_Time_List,
            trim( Parsed_List( LIB_K_OUTPUT_FORMAT )[ 0 ] ) + ' ' +
            trim( Parsed_List( LIB_K_OUTPUT_FORMAT )[ 1 ] ) ) ;
    end ;
    Result := _Parsed_Output_Date_Time_List ;
end ;


function TDate_Time_Context.Parsed_Output_Date_List : TStringList ;

begin
    if( _Parsed_Output_Date_List = nil ) then
    begin
        _Parsed_Output_Date_List := TStringList.Create ;
        Parse_String( _Parsed_Output_Date_List,
            trim( Parsed_List( LIB_K_OUTPUT_FORMAT )[ 0 ] ) ) ;
    end ;
    Result := _Parsed_Output_Date_List ;
end ;


function TDate_Time_Context.Parsed_Output_Time_List : TStringList ;

begin
    if( _Parsed_Output_Time_List = nil ) then
    begin
        _Parsed_Output_Time_List := TStringList.Create ;
        Parse_String( _Parsed_Output_Time_List, Parsed_List( LIB_K_OUTPUT_FORMAT )[ 1 ] ) ;
    end ;
    Result := _Parsed_Output_Time_List ;
end ;
Parsed_Output_Time_List returns the output format specification for time. LIB_K_OUTPUT_FORMAT is a two-item list: the first one is the date format and the second is the time format, so we ask for item 1 in the parsed list.

Parsed_Output_Date_List returns the output format specification for date. So we get item 0 from the list.

Parsed_Output_Date_Time_List returns the combined date and time output format. So we return the date format, a space, and the time format.

function TDate_Time_Context.Parsed_List( Index : integer ) : TStringList ;

begin
    Result := nil ;
    if( ( Index < 0 ) or ( Index > high( _Parsed_Lists ) ) ) then
    begin
        exit ;
    end ;
    if( _Parsed_Lists[ Index ].Count = 0 ) then
    begin
        if( Index = LIB_K_INPUT_FORMAT ) then
        begin
            Parse_Inputs ;
        end else
        begin
            _Parse_List( Index ) ;
        end ;
    end ;
    Result := _Parsed_Lists[ Index ] ;
end ;
This method returns the parsed list associated with the index. If the given list is empty (count 0), we call _Parse_List to parse the items for the component. The input format is a special case - it isn't a delimited list, but a single item containing date/time codes - so it has to be processed into a list in a different way.

// API...

procedure TDate_Time_Context.Load_Language_Date_Time_Formats( Language : string ) ;

begin
    if( Language = '' ) then
    begin
        Language := Get_Format( '', 'sys$language', _Components[ LIB_K_LANGUAGE ], '' ) ;
        if( Language = '' ) then
        begin
            Language := 'ENGLISH' ;
            Defaulted_To_English := True ;
        end ;
    end ;
    _Components[ LIB_K_MONTH_NAME_U ] := Get_Format( 'language_' + Language, 'LIB$MONTHS_U', 
        _Components[ LIB_K_MONTH_NAME_U ], 
        '|JANUARY|FEBRUARY|MARCH|APRIL|MAY|JUNE|JULY|AUGUST|SEPTEMBER|OCTOBER|NOVEMBER|DECEMBER|' ) ;
    _Components[ LIB_K_MONTH_NAME_L ] := Get_Format( 'language_' + Language, 'LIB$MONTHS_L', 
        _Components[ LIB_K_MONTH_NAME_L ], lowercase( _Components[ LIB_K_MONTH_NAME_U ] ) ) ;
    _Components[ LIB_K_MONTH_NAME_C ] := Get_Format( 'language_' + Language, 'LIB$MONTHS_C', 
        _Components[ LIB_K_MONTH_NAME_C ], Mix( _Components[ LIB_K_MONTH_NAME_L ] ) ) ;
    _Components[ LIB_K_MONTH_NAME_ABB_U ] := Get_Format( 'language_' + Language, 'LIB$MONTH_ABBREVIATIONS_U', 
        _Components[ LIB_K_MONTH_NAME_ABB_U ], '|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC|' ) ;
    _Components[ LIB_K_MONTH_NAME_ABB_L ] := Get_Format( 'language_' + Language, 'LIB$MONTH_ABBREVIATIONS_L', 
        _Components[ LIB_K_MONTH_NAME_ABB_L ], lowercase( _Components[ LIB_K_MONTH_NAME_ABB_U ] ) ) ;
    _Components[ LIB_K_MONTH_NAME_ABB_C ] := Get_Format( 'language_' + Language, 'LIB$MONTH_ABBREVIATIONS_C', 
        _Components[ LIB_K_MONTH_NAME_ABB_C ], Mix( _Components[ LIB_K_MONTH_NAME_ABB_L ] ) ) ;
    _Components[ LIB_K_FORMAT_MNEMONICS ] := Get_Format( 'language_' + Language, 'LIB$FORMAT_MNEMONICS', 
        _Components[ LIB_K_FORMAT_MNEMONICS ], '|YYYY|MM|DD|HH|MM|SS|CC|AM/PM|MONTH|' ) ;
    _Components[ LIB_K_WEEKDAY_NAME_U ] := Get_Format( 'language_' + Language, 'LIB$WEEKDAYS_U', 
        _Components[ LIB_K_WEEKDAY_NAME_U ], '|MONDAY|TUESDAY|WEDNESDAY|THURSDAY|FRIDAY|SATURDAY|SUNDAY|' ) ;
    _Components[ LIB_K_WEEKDAY_NAME_L ] := Get_Format( 'language_' + Language, 'LIB$WEEKDAYS_L', 
        _Components[ LIB_K_WEEKDAY_NAME_L ], lowercase( _Components[ LIB_K_WEEKDAY_NAME_U ] ) ) ;
    _Components[ LIB_K_WEEKDAY_NAME_C ] := Get_Format( 'language_' + Language, 'LIB$WEEKDAYS_C', 
        _Components[ LIB_K_WEEKDAY_NAME_C ], Mix( _Components[ LIB_K_WEEKDAY_NAME_L ] ) ) ;
    _Components[ LIB_K_WEEKDAY_NAME_ABB_U ] := Get_Format( 'language_' + Language, 
        'LIB$WEEKDAY_ABBREVIATIONS_U', 
        _Components[ LIB_K_WEEKDAY_NAME_ABB_U ], '|MON|TUE|WED|THU|FRI|SAT|SUN|' ) ;
    _Components[ LIB_K_WEEKDAY_NAME_ABB_L ] := Get_Format( 'language_' + Language, 
        'LIB$WEEKDAY_ABBREVIATIONS_L', 
        _Components[ LIB_K_WEEKDAY_NAME_ABB_L ], lowercase( _Components[ LIB_K_WEEKDAY_NAME_ABB_U ] ) ) ;
    _Components[ LIB_K_WEEKDAY_NAME_ABB_C ] := Get_Format( 'language_' + Language, 
        'LIB$WEEKDAY_ABBREVIATIONS_C', 
        _Components[ LIB_K_WEEKDAY_NAME_ABB_C ], Mix( _Components[ LIB_K_WEEKDAY_NAME_ABB_L ] ) ) ;
    _Components[ LIB_K_RELATIVE_DAY_NAME_U ] := Get_Format( 'language_' + Language, 'LIB$RELATIVE_DAYS_U', 
        _Components[ LIB_K_RELATIVE_DAY_NAME_U ], '|YESTERDAY|TODAY|TOMORROW|' ) ;
    _Components[ LIB_K_RELATIVE_DAY_NAME_L ] := Get_Format( 'language_' + Language, 'LIB$RELATIVE_DAYS_L', 
        _Components[ LIB_K_RELATIVE_DAY_NAME_L ], lowercase( _Components[ LIB_K_RELATIVE_DAY_NAME_U ] ) ) ;
    _Components[ LIB_K_RELATIVE_DAY_NAME_C ] := Get_Format( 'language_' + Language, 'LIB$RELATIVE_DAYS_C', 
        _Components[ LIB_K_RELATIVE_DAY_NAME_C ], Mix( _Components[ LIB_K_RELATIVE_DAY_NAME_L ] ) ) ;
    _Components[ LIB_K_MERIDIEM_INDICATOR_U ] := Get_Format( 'language_' + Language, 'LIB$MI_U', 
        _Components[ LIB_K_MERIDIEM_INDICATOR_U ], '|AM|PM|' ) ;
    _Components[ LIB_K_MERIDIEM_INDICATOR_L ] := Get_Format( 'language_' + Language, 'LIB$MI_L', 
        _Components[ LIB_K_MERIDIEM_INDICATOR_L ], lowercase( _Components[ LIB_K_MERIDIEM_INDICATOR_U ] ) ) ;
    _Components[ LIB_K_MERIDIEM_INDICATOR_C ] := Get_Format( 'language_' + Language, 'LIB$MI_C', 
        _Components[ LIB_K_MERIDIEM_INDICATOR_C ], Mix( _Components[ LIB_K_MERIDIEM_INDICATOR_L ] ) ) ;
    _Components[ LIB_K_OUTPUT_FORMAT ] := Get_Format( '', 'LIB$DT_FORMAT', _Components[ LIB_K_OUTPUT_FORMAT ], 
        '|!DB-!MAAU-!Y4 | !H04:!M0:!S0.!C2|' ) ;
    _Components[ LIB_K_INPUT_FORMAT ] := Get_Format( '', 'LIB$DT_INPUT_FORMAT', 
        _Components[ LIB_K_INPUT_FORMAT ], '!DB-!MAAU-!Y4:!H04:!M0:!S0.!C2' ) ;
    _Components[ LIB_K_LANGUAGE ] := Language ;
end ;
The Load_Language_Date_Time_Formats method loads the components for the specified language. We default to English values if there aren't corresponding values for the selected language. Thus, there are always component values available in all cases. If the component is already set, we leave it as it is. Get_Format obtains the value for a given component from the appropriate language table, and symbol within that table. If that symbol is not defined, and there is no existing value of the component (in case it was set earlier), it uses the English default.

Let's follow the process for the first component (assuming that the language is english). We ask Get_Format for the uppercase month name list. If the LIB_K_MONTH_NAME_U index of components is already set, Get_Format returns it as it is. Otherwise Get_Format looks in the "LNM$language_English" table for the "LIB$MONTHS_U" symbol and we assign that value to the component. If that was empty or not found, we default to the English month names of '|JANUARY|FEBRUARY|MARCH|APRIL|MAY|JUNE|JULY|AUGUST|SEPTEMBER|OCTOBER|NOVEMBER|DECEMBER|'.

There are several components that make up the localized definitions for dates and times. The LIB_K_LANGUAGE index in the _Components array contains the language that the rest of the components correspond to, for instance. Most of these items come in three "flavors": uppercase, lowercase, and mixed case (using the "_U", "_L", and "_C" suffixes on the indexes). For instance LIB_K_MONTH_NAME_U indicates the uppercase version of month names, LIB_K_MONTH_NAME_L indicates the lowercase version of the same, and LIB_K_MONTH_NAME_C is the mixed case version. The system administrator can define a symbol for each of these, but the above code will default the lower- and mixed- case version from the uppercase version. Thus, most of the time, only a single symbol for each set needs to be defined. Mixed case will change the first character of each item to uppercase and the rest to lowercase. If this is not the desired behavior, a symbol can be used to explicitly define it.

function TDate_Time_Context.Index( Component : integer ; S : string ;
    St : integer ; var En : integer ) : integer ;

var Found : boolean ;
    I : integer ;
    SL : TStringList ;
    T : string ;

begin
    // Setup...
    Result := 0 ; // Assume failure
    S := lowercase( S ) ;
    Found := False ;

    case Component of
        LIB_K_MONTH_NAME_U, LIB_K_MONTH_NAME_C : Component := LIB_K_MONTH_NAME_L ;
        LIB_K_MONTH_NAME_ABB_U, LIB_K_MONTH_NAME_ABB_C : Component := LIB_K_MONTH_NAME_ABB_L ;
        LIB_K_WEEKDAY_NAME_U, LIB_K_WEEKDAY_NAME_C : Component := LIB_K_WEEKDAY_NAME_L ;
        LIB_K_WEEKDAY_NAME_ABB_U, LIB_K_WEEKDAY_NAME_ABB_C : Component := LIB_K_WEEKDAY_NAME_ABB_C ;
        LIB_K_RELATIVE_DAY_NAME_U, LIB_K_RELATIVE_DAY_NAME_C : Component := LIB_K_RELATIVE_DAY_NAME_C ;
        LIB_K_MERIDIEM_INDICATOR_U, LIB_K_MERIDIEM_INDICATOR_C : Component := LIB_K_MERIDIEM_INDICATOR_L ;
    end ;
    SL := Parsed_List( Component ) ;
    
    for I := 0 to SL.Count - 1 do
    begin
        T := SL[ I ] ;
        if( copy( S, St, length( T ) ) = T ) then
        begin
            if( Found ) then // Non-unique abbreviation
            begin
                Result := 0 ;
                exit ;
            end ;
            En := St + length( S ) - 1 ;
            Result := I + 1 ;
            Found := True ;
        end ;
    end ; // for I := 0 to SL.Count - 1
    if( not Found ) then
    begin
        Result := 0 ;
    end ;
end ; // TDate_Time_Context.Index
This function finds the index of a string in the specified component's list of values. We start by converting the search string to lowercase, then we make sure the component selected is converted to the lowercase version of that component. There are three versions of each component: uppercase, lowercase, and mixed case. They are supposed to be identical except for case. By converting the string to lowercase and choosing the lowercase version of the component, we can compare with each element in the list. Which is what the code does next. For each item in the chosen parsed list, we see if the search string matches the prefix of the item. If so, we return the index. Note that we return the index plus 1, so the result is 1-based (a value of 0 indicates the string wasn't found).

Although we allow abbreviated versions of what is in the list, the abbreviation must be unique or else we return 0 to indicate a failure. However, if we find an exact match, we immediately exit.

function TDate_Time_Context.Parameter_Counts( Component : integer ; const Value : string ) : boolean ;

var C : char ;
    I : integer ;

begin
    Result := True ;
    case Component of
        LIB_K_MONTH_NAME_U,
            LIB_K_MONTH_NAME_L,
            LIB_K_MONTH_NAME_C : Component := 12 ;
        LIB_K_MONTH_NAME_ABB_U,
            LIB_K_MONTH_NAME_ABB_L,
            LIB_K_MONTH_NAME_ABB_C : Component := 12 ;
        LIB_K_FORMAT_MNEMONICS : Component := 9 ;
        LIB_K_WEEKDAY_NAME_U,
            LIB_K_WEEKDAY_NAME_L,
            LIB_K_WEEKDAY_NAME_C : Component := 7 ;
        LIB_K_WEEKDAY_NAME_ABB_U,
            LIB_K_WEEKDAY_NAME_ABB_L,
            LIB_K_WEEKDAY_NAME_ABB_C : Component := 7 ;
        LIB_K_RELATIVE_DAY_NAME_U,
            LIB_K_RELATIVE_DAY_NAME_L,
            LIB_K_RELATIVE_DAY_NAME_C : Component := 3 ;
        LIB_K_MERIDIEM_INDICATOR_U,
            LIB_K_MERIDIEM_INDICATOR_L,
            LIB_K_MERIDIEM_INDICATOR_C : Component := 2 ;
        LIB_K_OUTPUT_FORMAT : Component := 2 ;
        else exit ;
    end ;
    if( length( Value ) > 1 ) then
    begin
        C := Value[ 1 ] ;
    end ;
    for I := 1 to length( Value ) do
    begin
        if( Value[ I ] = C ) then
        begin
            dec( Component ) ;
        end ;
    end ;
    Result := Component = -1 ;
end ;
This method indicates whether the number of items required to be in the list for a given component index are found in the passed string. We simply set the Component to the number of required components, then iterate through the string, counting the number of delimiters (defined by the first character). Since there is always a leading and trailing delimiter, there will be one more delimiter than the number of actual items in the string. So, if everything is right, Component will be -1. We return true if that is the case and false otherwise.

var _Default_Time_Context : TDate_Time_Context ;

function Default_Time_Context : TDate_Time_Context ;

begin
    if( _Default_Time_Context = nil ) then
    begin
        _Default_Time_Context := TDate_Time_Context.Create ;
    end ;
    Result := _Default_Time_Context ;
end ;
The Default_Time_Context function returns a TDate_Time_Context instance. If one hasn't been set already, it first creates one, and then returns it. The reason for this is partly because our philosophy has been to only do work when it is requested, to avoid startup overhead. But even more important is the fact that a program can set a date/time context that all startlet functions will thereafter use for that run of the program. In such case, the _Default_Time_Context variable will be set to that instance. We will describe how this is set up in a future article. Thus, not only do date/time context instances serve as an interface to the localization settings defined by the various tables and symbols, but it also allows a program to define a custom date/time format that the startlet functions will use.

This article is long enough, so in the next article, we'll finish up with the remaining starlet functions used in the previous articles.

 

Copyright © 2020 by Alan Conroy. This article may be copied in whole or in part as long as this copyright is included.