{
  "category": "Our 10 featured functions",
  "display_name": "TEXTBEFORE",
  "entry_kind": "built_in_function",
  "eval_api": {
    "method": "POST",
    "url": "https://handbook.dnacalc.com/api/eval"
  },
  "function_id": "FUNC.TEXTBEFORE",
  "implementation_state": {
    "answer_bank": {
      "excel_builds": [
        "20527"
      ],
      "larger_numeric": 0,
      "last_bit": 0,
      "matched": 11,
      "other_mismatch": 0,
      "refused": 0,
      "rows": 11,
      "unread": 0,
      "worst_ulp": 0
    },
    "detail": "Recorded: 11 in the answer bank and 11 workbook cells.",
    "dnacalc_commit": "9e1884369a678158dd82d3d74188daf986995e87",
    "in_catalogue": true,
    "state": "matches",
    "state_label": "Matches every recorded Excel answer",
    "summary": "Matches all 22 recorded answers",
    "workbook_corpus": {
      "cells": 11,
      "matched": 11
    }
  },
  "implementations": [
    {
      "commit": "9e1884369a678158dd82d3d74188daf986995e87",
      "flavour": "excel-compatibility-candidate",
      "language": "Rust",
      "name": "CalcFunc",
      "own_code": [
        "https://github.com/DnaCalc/DnaCalc/blob/9e1884369a678158dd82d3d74188daf986995e87/crates/calc-func/src/catalogue/text_format.rs#L932-L932"
      ],
      "repository": "https://github.com/DnaCalc/DnaCalc",
      "shared_code": [
        "catalogue/text_format::read_value",
        "catalogue/text_format::value",
        "catalogue/text_format::profile",
        "catalogue/text_format::rendered",
        "catalogue/text_format::text",
        "catalogue/text_format::given",
        "catalogue/text_format::binary_flag",
        "catalogue/text_format::delimiters",
        "catalogue/text_format::text_delim",
        "decimal::low32",
        "decimal::Natural::zero",
        "decimal::Natural::from_u64",
        "decimal::Natural::pow10",
        "decimal::Natural::normalize",
        "decimal::Natural::is_zero",
        "decimal::Natural::mul_add_small",
        "decimal::Natural::times_pow10",
        "decimal::Natural::times_pow5",
        "decimal::Natural::times_pow2",
        "decimal::ExactDecimal::read",
        "decimal::ExactDecimal::binary",
        "format/datetime::has_invalid_exponent",
        "format/datetime::looks_like_datetime_format",
        "format/datetime::without_subseconds",
        "format/datetime::render_with_datetime_tokens",
        "format/datetime::tokenize_datetime_format",
        "format/datetime::elapsed_token_at",
        "format/datetime::classify_minute_tokens",
        "format/datetime::token_kind",
        "format/datetime::is_time_neighbor",
        "format/datetime::push_literal",
        "format/datetime::render_datetime_literal",
        "format/datetime::format_elapsed",
        "format/datetime::TimeParts::from_serial",
        "format::measured_host",
        "format::currency_literal",
        "format::digits",
        "format::fifteen",
        "format::split",
        "format::grouped",
        "format::general",
        "format::significant",
        "format::sections",
        "format::bracket",
        "format::is_condition",
        "format::is_elapsed",
        "format::condition_holds",
        "format::condition_of",
        "format::strip_tokens",
        "format::choose",
        "format::items",
        "format::numeric",
        "format::literal_text",
        "format::integer_part",
        "format::fraction_part",
        "format::scientific",
        "format::fraction_code",
        "format::FractionCode::render",
        "format::mixed_fraction_under_space_grouping",
        "format::convergent",
        "format::text_declared_date_locale",
        "format::text",
        "format::text_of_text",
        "format/names::month_name",
        "format/names::weekday_name",
        "format/names::locale_names",
        "locale::LocaleId::from_bcp47_language_tag",
        "locale::LocaleId::from_excel_lcid",
        "locale::short_date_order",
        "locale::short_date_pattern",
        "locale::currency_placement",
        "locale::currency_spacing",
        "locale::currency_negative_pattern",
        "locale::format_profile",
        "locale::days_from_civil",
        "locale::civil_from_days",
        "locale::ymd_from_excel_serial",
        "number_text::decimal",
        "number_text::first_precision",
        "number_text::near_tie",
        "number_text::first_rounding",
        "number_text::rounded",
        "number_text::increment",
        "number_text::ascii",
        "ported/date_kernels::days_from_civil",
        "ported/date_kernels::civil_from_days",
        "ported/date_kernels::serial_unbounded_1900",
        "ported/date_kernels::ymd_1900",
        "ported/date_kernels::is_leap",
        "ported/date_kernels::days_in_month",
        "ported/date_kernels::whole_serial",
        "ported/date_kernels::whole",
        "ported/date_kernels::weekday_sunday_one_based",
        "ported/date_kernels::remap_weekday",
        "ported/date_kernels::weekday_of",
        "ported/date_kernels::weekday",
        "ported/text_kernels::text",
        "ported/text_kernels::text_from_str",
        "ported/text_kernels::date_integer_floor",
        "ported/text_kernels::push_singleton_or_original",
        "ported/text_kernels::push_upper",
        "ported/text_kernels::upper",
        "ported/text_more::fold_ascii_case",
        "ported/text_more::units_match",
        "ported/text_more::delimited",
        "value_text::measured_host_reader",
        "value_text::measured_space",
        "value_text::read",
        "value_text::number",
        "value_text::decimal",
        "value_text::fraction",
        "value_text::tokens",
        "value_text::month_named",
        "value_text::time_at",
        "value_text::full_year",
        "value_text::valid_day",
        "value_text::this_year",
        "value_text::serial_1900",
        "value_text::date_of",
        "value_text::date_time"
      ],
      "source": "https://github.com/DnaCalc/DnaCalc/blob/9e1884369a678158dd82d3d74188daf986995e87/crates/calc-func/src/catalogue/text_format.rs#L932-L932",
      "state": "matches",
      "suite": null,
      "summary": "Matches all 22 recorded answers"
    }
  ],
  "json_url": "https://handbook.dnacalc.com/api/functions/FUNC.TEXTBEFORE.json",
  "name": "TEXTBEFORE",
  "notes": [
    {
      "basis": "Three recorded readings, with blank-cell and logical companions in the same run.",
      "documentation": null,
      "evidence": [
        {
          "commit": "080a6f2f7b65e750400837841f9db3d3822d8ec3",
          "locator": "empty_text_delimiter_errors_match_the_recorded_excel_values",
          "note": "recorded readings pinned",
          "path": "crates/calc-func/tests/f2_text.rs",
          "repository": "DnaCalc"
        }
      ],
      "functions": [
        "FUNC.TEXTAFTER",
        "FUNC.TEXTBEFORE",
        "FUNC.TEXTSPLIT"
      ],
      "inferred": null,
      "kind": "quirk",
      "note_id": "FN-TEXTSPLIT-001",
      "open": [],
      "recorded_on": "2026-11-04",
      "repros": [
        {
          "call": "=TEXTBEFORE(\"\",\"-\")",
          "correct": null,
          "excel": {
            "bits": null,
            "display": "#N/A",
            "kind": "error"
          },
          "input_route": "formula-literal",
          "inputs": [],
          "recorded_in": "Excel build not recorded in this run; bulk captures of the same date (2026-09-29) record Excel 16.0 build 20430, Windows 64-bit, workbook compatibility 2",
          "source": {
            "commit": "4d5320dfdc385354469b4d506153357bb932ab08",
            "locator": "w111typed-00274-textbefore-arg1-empty-text",
            "note": null,
            "path": "runs/w111-broad-typed-20260929-002/outcomes/excel.jsonl.gz",
            "repository": "CalcFunc-Evidence"
          }
        },
        {
          "call": "=TEXTAFTER(\"\",\"-\")",
          "correct": null,
          "excel": {
            "bits": null,
            "display": "#N/A",
            "kind": "error"
          },
          "input_route": "formula-literal",
          "inputs": [],
          "recorded_in": "Excel build not recorded in this run; bulk captures of the same date (2026-09-29) record Excel 16.0 build 20430, Windows 64-bit, workbook compatibility 2",
          "source": {
            "commit": "4d5320dfdc385354469b4d506153357bb932ab08",
            "locator": "w111typed-00266-textafter-arg1-empty-text",
            "note": null,
            "path": "runs/w111-broad-typed-20260929-002/outcomes/excel.jsonl.gz",
            "repository": "CalcFunc-Evidence"
          }
        },
        {
          "call": "=TEXTSPLIT(\"\",\",\")",
          "correct": null,
          "excel": {
            "bits": null,
            "display": "#VALUE!",
            "kind": "error"
          },
          "input_route": "formula-literal",
          "inputs": [],
          "recorded_in": "Excel build not recorded in this run; bulk captures of the same date (2026-09-29) record Excel 16.0 build 20430, Windows 64-bit, workbook compatibility 2",
          "source": {
            "commit": "4d5320dfdc385354469b4d506153357bb932ab08",
            "locator": "w111typed-00282-textsplit-arg1-empty-text",
            "note": null,
            "path": "runs/w111-broad-typed-20260929-002/outcomes/excel.jsonl.gz",
            "repository": "CalcFunc-Evidence"
          }
        }
      ],
      "schema": "efh.function-note/v1",
      "statement": "An empty string as text gives #N/A (delimiter not found) from TEXTBEFORE and TEXTAFTER, but #VALUE! from TEXTSPLIT.",
      "status": "measured",
      "title": "Empty text gives #N/A from TEXTBEFORE and TEXTAFTER, #VALUE! from TEXTSPLIT"
    }
  ],
  "schema": "efh.function-reference/v1",
  "section_order": [
    "what-it-does",
    "try-it",
    "quirks",
    "how-to-implement",
    "version-differences",
    "alternatives"
  ],
  "sections": {
    "alternatives": "\n### Relationships\n\n\n- [`TEXTAFTER`](FUNC.TEXTAFTER.md) — the mirror function; identical arguments, opposite side of the\n  cut, shared implementation module upstream.\n- [`TEXTSPLIT`](FUNC.TEXTSPLIT.md) — cuts at every occurrence at once and returns an array.\n- `LEFT`, `MID` — positional slicing, for when you know the index rather than the marker.\n- `FIND` / `SEARCH` with `LEFT` is the legacy idiom, still fully supported. `LEFT(t, FIND(d,t)-1)`\n  is roughly `TEXTBEFORE(t, d)` except that it errors instead of returning `#N/A`, has no negative\n  indexing, and needs `SEARCH` swapped in for case-insensitive matching.\n- `TEXTBEFORE` supersedes nothing; the older composition remains valid.\n\n\n",
    "how-to-implement": "\n### Notes for implementers\n\n\n- **Fix an overlap policy for multi-character delimiters and record it.** Scanning `\"aaa\"` for\n  `\"aa\"` gives a different occurrence count depending on whether you advance by one character or\n  past the match. Microsoft's page does not state which Excel does.\n- **Resolve negative `instance_num` against the same occurrence list** you built left to right. A\n  separate right-to-left scan can disagree for overlapping delimiters.\n- **Case-insensitive matching is a folding choice.** The upstream contract records ASCII folding\n  only as observed. A full Unicode fold may find occurrences Excel does not.\n- **String indices are UTF-16 code units** in this Handbook's model; astral characters make the\n  choice observable. See [the value universe](../model/01-value-universe.md).\n- **Do not lift the delimiter position on symmetry grounds.** The evidence admits `text` and\n  `instance_num`; inventing the third is how invented behaviour ships.\n\n\n",
    "quirks": "\n### Result and edge cases\n\n\nThe return kind is `Text`, possibly the empty string. The function is a scalar kernel; array\narguments are handled by the engine's lifting, described in\n[coercion and lifting](../model/02-coercion-and-lifting.md), and the spill of a lifted result is\nthe host-side adaptation step in [the call pipeline](../model/03-call-pipeline.md).\n\nFurther boundaries:\n\n- **Non-text arguments** convert by the ordinary rules; `TRUE` becomes the string `TRUE`, a number\n  becomes its general-format rendering.\n- **An empty referenced cell** delivers `Empty`, which the model keeps distinct from an omitted\n  argument — see [the value universe](../model/01-value-universe.md).\n- **Errors propagate** rather than being absorbed by coercion.\n- **Empty string and `#N/A` mean different things.** Empty means the cut succeeded with nothing to\n  its left; `#N/A` (or `if_not_found`) means the occurrence did not exist. A caller that treats\n  them alike will silently mishandle leading delimiters.\n\n\n\n### Errors\n\n\nThe error table below is taken from Microsoft's **`TEXTAFTER`** page, which the Handbook retrieved\nwhile writing this entry; the `TEXTBEFORE` page was not retrievable at that time. The two functions\nshare an argument set, so the same conditions are the expected ones here — but that is an\ninference, and the `TEXTBEFORE` page is the authority for `TEXTBEFORE`.\n\n| Error | Condition (documented for `TEXTAFTER`; expected for `TEXTBEFORE`) |\n|---|---|\n| `#N/A` | The delimiter is not found, or `instance_num` exceeds the number of occurrences. |\n| `#VALUE!` | `instance_num` is `0`, or its magnitude exceeds the length of the text. |\n\n`if_not_found` replaces the `#N/A` only. Errors arriving in arguments propagate ahead of both rows.\nEmpty `text` gives `#N/A`, not `#VALUE!`, even though the default `instance_num` of 1 exceeds its\nlength of 0 (FN-TEXTSPLIT-001).\n\n\n",
    "sources": "- Microsoft Support, TEXTBEFORE function —\n  <https://support.microsoft.com/en-us/office/textbefore-function-d099c28a-dba8-448e-ac6c-f086d0fa1b29>\n  (the authority for this function; not retrievable while this entry was written, so it is cited by\n  reference and nothing here paraphrases it).\n- Microsoft Support, TEXTAFTER function —\n  <https://support.microsoft.com/en-us/office/textafter-function-c8db2546-5b51-416a-9690-c7e6722e90b4>\n  (retrieved for this page; carries the shared argument and error tables).\n- Handbook `content/model/01-value-universe.md`, `02-coercion-and-lifting.md`,\n  `03-call-pipeline.md`.\n\n",
    "version-differences": "No comparison between specific Excel builds is recorded here yet. This does not establish that all builds behave alike.",
    "what-it-does": "\n`TEXTBEFORE` slices a string at one chosen occurrence of a delimiter and returns everything to the\nleft of it.\n\nThe machinery is identical to [`TEXTAFTER`](FUNC.TEXTAFTER.md) and only the cut point differs. Let\n`t` be the text and `d` the delimiter. Under the chosen match mode, enumerate the occurrences of\n`d` in `t` left to right as index pairs `(start_1, end_1), …, (start_k, end_k)` with\n`end_i = start_i + len(d)`. `instance_num` selects one pair — positive counts from the left,\nnegative from the right. `TEXTBEFORE` returns the substring of `t` from position `0` up to the\nselected `start_i`. `TEXTAFTER` returns the substring from `end_i` to the end.\n\nThat single-symbol difference explains the whole family:\n\n1. **The delimiter is in neither result.** `TEXTBEFORE` stops at `start_i`; `TEXTAFTER` resumes at\n   `end_i`. Concatenating the two for the same occurrence reconstructs `t` minus one copy of the\n   delimiter, not `t`.\n2. **A delimiter at position `0` yields the empty string.** `start_1 = 0` and the substring is\n   empty. That is a successful cut, not a failure.\n3. **Occurrence indexing, not character indexing.** For positional work use `LEFT`/`MID`.\n\n\n### Arguments\n\n\n`TEXTBEFORE(text, delimiter, [instance_num], [match_mode], [match_end], [if_not_found])`\n\nArgument meanings below follow Microsoft's documentation for the shared `TEXTAFTER`/`TEXTBEFORE`\nargument set, as retrieved from the `TEXTAFTER` page.\n\n| Argument | Required | Meaning |\n|---|---|---|\n| `text` | yes | The string being searched. Wildcards are not accepted; the delimiter matches literally. |\n| `delimiter` | yes | The string that marks the cut point. May be multi-character. |\n| `instance_num` | no | Which occurrence to cut at. Defaults to `1`; negative counts from the end; `0` is documented as an error. |\n| `match_mode` | no | `0` (default) case-sensitive, `1` case-insensitive. |\n| `match_end` | no | `0` (default) plain matching; `1` treats the end of the text as a delimiter. |\n| `if_not_found` | no | The value returned instead of `#N/A` when the requested occurrence does not exist. |\n\nThe third position is the one most often misread: `instance_num` is a signed occurrence index, not\na character count. `TEXTBEFORE(\"a-b-c\", \"-\", -1)` is `\"a-b\"` — everything before the *last*\nhyphen — which is the idiom for \"strip the final segment\" and is not obtainable by any positive\nindex without first counting the delimiters.\n\n`if_not_found` catches only the no-match case. An argument-domain error still surfaces through it.\n\n\n"
  },
  "signature": {
    "display": "TEXTBEFORE(text, delimiter, [instance_num], [match_mode], [match_end], [if_not_found])",
    "parameters": [
      {
        "description": null,
        "name": "text",
        "optional": false,
        "repeats": false
      },
      {
        "description": null,
        "name": "delimiter",
        "optional": false,
        "repeats": false
      },
      {
        "description": null,
        "name": "instance_num",
        "optional": true,
        "repeats": false
      },
      {
        "description": null,
        "name": "match_mode",
        "optional": true,
        "repeats": false
      },
      {
        "description": null,
        "name": "match_end",
        "optional": true,
        "repeats": false
      },
      {
        "description": null,
        "name": "if_not_found",
        "optional": true,
        "repeats": false
      }
    ],
    "trailing_repeats": false
  },
  "sources": {
    "metadata": {
      "admission": {
        "label": "implemented",
        "reason": null
      },
      "arity": {
        "max": 6,
        "min": 2
      },
      "category": "Our 10 featured functions",
      "classification": {
        "arg_preparation_profile": "ValuesOnlyPreAdapter",
        "coercion_lift_profile": "Custom",
        "determinism_class": "Deterministic",
        "error_collapse_profile": "None",
        "fec_dependency_profile": "None",
        "host_interaction_class": "None",
        "kernel_signature_class": "TextToText",
        "rich_value_usage": "RichBlind",
        "surface_fec_dependency_profile": "RefOnly",
        "thread_safety_class": "SafePure",
        "volatility_class": "NonVolatile"
      },
      "descriptions": {
        "long": null,
        "short": "The part of a text before a delimiter",
        "short_source": "dna-calc-own"
      },
      "docs": {
        "english_description": "Use this function to return text that occurs before a given character or string. You can use TEXTAFTER to return text that occurs after a given character or string.",
        "microsoft_url": "https://support.microsoft.com/en-us/office/textbefore-function-d099c28a-dba8-448e-ac6c-f086d0fa1b29"
      },
      "entry_kind": "built_in_function",
      "localized_family_names": null,
      "localized_names": [
        {
          "locale": "ar-SA",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "bg-BG",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "cs-CZ",
          "name": "TEXTPŘED"
        },
        {
          "locale": "da-DK",
          "name": "TEKSTFØR"
        },
        {
          "locale": "de-DE",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "el-GR",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "en-AU",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "en-GB",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "en-US",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "es-ES",
          "name": "TEXTOANTES"
        },
        {
          "locale": "et-EE",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "fi-FI",
          "name": "TEKSTI.ENNEN"
        },
        {
          "locale": "fr-FR",
          "name": "TEXTE.AVANT*"
        },
        {
          "locale": "he-IL",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "hr-HR",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "hu-HU",
          "name": "ELŐTTE LÉVŐ SZÖVEG"
        },
        {
          "locale": "id-ID",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "it-IT",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "ja-JP",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "ko-KR",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "lt-LT",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "lv-LV",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "nb-NO",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "nl-NL",
          "name": "TEKST.VOOR"
        },
        {
          "locale": "pl-PL",
          "name": "TEKST.PRZED"
        },
        {
          "locale": "pt-BR",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "pt-PT",
          "name": "TEXTOANTES"
        },
        {
          "locale": "ro-RO",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "ru-RU",
          "name": "ТЕКСТДО"
        },
        {
          "locale": "sk-SK",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "sl-SI",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "sr-Latn-RS",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "sv-SE",
          "name": "TEXTFÖRE"
        },
        {
          "locale": "th-TH",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "tr-TR",
          "name": "ÖNCEKİMETİN"
        },
        {
          "locale": "uk-UA",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "vi-VN",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "zh-CN",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "zh-HK",
          "name": "TEXTBEFORE"
        },
        {
          "locale": "zh-TW",
          "name": "TEXTBEFORE"
        }
      ],
      "signature": {
        "display": "TEXTBEFORE(text, delimiter, [instance_num], [match_mode], [match_end], [if_not_found])",
        "parameters": [
          {
            "description": null,
            "name": "text",
            "optional": false,
            "repeats": false
          },
          {
            "description": null,
            "name": "delimiter",
            "optional": false,
            "repeats": false
          },
          {
            "description": null,
            "name": "instance_num",
            "optional": true,
            "repeats": false
          },
          {
            "description": null,
            "name": "match_mode",
            "optional": true,
            "repeats": false
          },
          {
            "description": null,
            "name": "match_end",
            "optional": true,
            "repeats": false
          },
          {
            "description": null,
            "name": "if_not_found",
            "optional": true,
            "repeats": false
          }
        ],
        "trailing_repeats": false
      },
      "surface_name": "TEXTBEFORE",
      "version_marker": null,
      "xlcall": null
    },
    "microsoft": "https://support.microsoft.com/en-us/office/textbefore-function-d099c28a-dba8-448e-ac6c-f086d0fa1b29"
  },
  "try_it": {
    "formula": "=TEXTBEFORE(\"name@example.com\",\"@\")"
  },
  "url": "https://handbook.dnacalc.com/functions/TEXTBEFORE.html",
  "variants": [],
  "versions": []
}