# Thu, 20 Aug 2026 13:11:52 GMT
---
version: 23
data:


- type: ModificationMethod
  attributes:
    displayName: Czech birthday number
    description: |
          Generates a Czech birthday number based on the birthday and sex parameters. The generated number is returned as a string.
          If the value is a date it will be used as is. All other values will be hashed.
    scriptLanguage: "GROOVY"
    legacyScript: false
    inputType: "COLUMN"
    mappingTableContainers: []
    mappingTableAccessMode: READ
    code: |
        /*
         * *****************************************************************************
         * The following variables are available in this script:
         * =====================================================
         * ctx             : A context object that offers a number of methods.
         *                   The method indexOf() can be used to get the index of a
         *                   specific column within the dataArray.
         * data            : The data array. This array contains the contents of all
         *                   columns of the current row of the table that is being
         *                   masked by this method.
         *
         * Additionally for column based modification methods the following two
         * variables are available:
         * columnName      : The name of the column that matched the pattern.
         * columnIndex     : The position of the column that matched the pattern within
         *                   the data array. This value is zero-based.
         * *****************************************************************************
         */
        
        import java.text.SimpleDateFormat
        import de.ubs.xdm3.batch.modification.HashUtils
        import de.ubs.xdm3.batch.modification.ModificationException
        
        import java.time.LocalDate
        import java.time.OffsetDateTime
        import java.time.format.DateTimeFormatter
        
        dateFormatter = null
        sexes = ['M', 'F']
        
        /*
         * This function is called once before the processing starts. The method
         * is called once per table.
         */
        def init() {
            dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
            return null
        }
        
        /*
         * This function should modify the row that is provided by the dataArray. The
         * method is called for each single row of the table.
         */
        def apply() {
            def birthdayValue = modificationMethod.birthday.toString()
            def regex = /^(\d{4}-\d{2}-\d{2})-(\d{2})\.(\d{2})\.(\d{2})\..*\+(\d{2}):(\d{2})$/
            def m = birthdayValue =~ regex
            if (m.matches()) {
                def normalized = "${m[0][1]}T${m[0][2]}:${m[0][3]}:${m[0][4]}+${m[0][5]}:${m[0][6]}"
                def odt = OffsetDateTime.parse(normalized)
                birthdayValue = odt.toLocalDate().format(dateFormatter)
            }
        
            LocalDate date
            try {
                date = LocalDate.parse(birthdayValue, dateFormatter)
            } catch (Exception e1) {
                try {
                    def dateOnly = birthdayValue.substring(0, 10)
                    date = LocalDate.parse(dateOnly, dateFormatter)
                    birthdayValue = dateOnly
                } catch (Exception e2) {
                    long hash = HashUtils.hashInt(HashUtils.hashStringLikeInt(birthdayValue.toString()) % 1_400_000_000)
                    long val = hash / (24*60*60*1000)
                    date = LocalDate.ofEpochDay(val)
                }
            }
        
        
            def sexValue
        
            def vals = modificationMethod.sex.toString().split(',').findAll { it }
            sexValue = vals[sexes.size() % vals.size()]
        
            if (!sexes.contains(sexValue)) {
                throw new ModificationException("APPLY:Sex '${sexValue}' is not valid. Allowed values: ${sexes.join('\',\'')}")
            }
        
            def isBefore54 = date.year < 1954
            def year = date.year % 100
            def month = date.monthValue
            def day = date.dayOfMonth
        
            year = year < 10 ? "0${year}" : "${year}"
            if (sexValue == 'F') {
                month += 50
            }
            month = month < 10 ? "0${month}" : "${month}"
            day = day < 10 ? "0${day}" : "${day}"
        
            def sssc
            if (isBefore54) {
                sssc = HashUtils.hashInt(HashUtils.hashStringLikeInt(birthdayValue.toString()), 999)
                sssc = sssc < 10 ? "00${sssc}" : (sssc < 100 ? "0${sssc}" : "${sssc}")
            } else {
                def index = 0
                do {
                    sssc = HashUtils.hashInt(HashUtils.hashStringLikeInt(birthdayValue.toString()), 9999) + index
                    sssc = sssc < 10 ? "000${sssc}" : (sssc < 100 ? "00${sssc}" : (sssc < 1000 ? "0${sssc}" : "${sssc}"))
                    index++
                } while (((year + month + day + sssc) as Long) % 11 != 0)
            }
            data[columnIndex] = "${year}${month}${day}${modificationMethod.delimiter}${sssc}"
            return true
        }
        
        /*
         * This function is executed once after the modification of a table is complete. The method
         * is called once per table.
         */
        def close() {
        }
    parameters:
    - displayName: Birthday
      description: |
          Specify a value that is used to calculate the hash.
      variableName: birthday
      dataType: STRING
    - displayName: Sex
      description: |
          Specifies the gender of the first name.
      variableName: sex
      dataType: STRING
    - displayName: Delimiter
      description: |
          The delimiter character used to separate the groups of digits. May be empty.
      variableName: delimiter
      defaultValue: "/"
      dataType: STRING
