Skip to content

feat: Add global locale support for number parsing#1165

Merged
nathan-stender merged 5 commits into
mainfrom
feat/global-locale-support
Apr 6, 2026
Merged

feat: Add global locale support for number parsing#1165
nathan-stender merged 5 commits into
mainfrom
feat/global-locale-support

Conversation

@nathan-stender

Copy link
Copy Markdown
Collaborator

Summary

Adds a locale parameter to top-level API functions (allotrope_from_io, allotrope_from_file, etc.) to support parsing numbers in locale-specific formats.

  • Add locale_number_parser module using Babel's CLDR data for locale-aware number parsing
  • Add locale_context module for thread-safe locale management using ContextVar
  • Update try_float() to respect locale context when parsing numbers
  • Add locale parameter to all top-level API functions in to_allotrope.py
  • Add babel >= 2.9.0 dependency
  • Support for US (1,234.56), German (1.234,56), French (1 234,56) formats
  • Handles scientific notation, NaN, infinity with locale-specific separators
  • Falls back to default parsing if locale parsing fails

Test plan

  • All existing tests pass (546 tests)
  • Added 29 tests for locale number parsing (formats, edge cases, error handling)
  • Added 8 tests for locale context management and integration
  • Added 3 API integration tests for locale parameter
  • Linting and type checking pass

Usage example:

from allotropy.to_allotrope import allotrope_from_file

# Parse file with German number format (1.234,56)
result = allotrope_from_file(
    "data.csv",
    "VENDOR_NAME",
    locale="de_DE"
)

🤖 Generated with Claude Code

Add locale parameter to top-level API functions to support
locale-specific number formats using Babel's CLDR data.

- Add locale_number_parser module with Babel-based parsing
- Add locale_context module for thread-safe locale management
- Update try_float() to respect locale context
- Add locale parameter to allotrope_from_io/file functions
- Add babel >= 2.9.0 dependency
- Add comprehensive tests for locale parsing and context

This enables parsing numbers in different locales (e.g., German
"1.234,56" vs US "1,234.56") by passing locale="de_DE" to the
top-level API functions.

Co-Authored-By: Claude Opus 4.1 <noreply@anthropic.com>
@nathan-stender
nathan-stender requested review from a team and slopez-b as code owners April 6, 2026 05:55
@nathan-stender
nathan-stender requested a review from tamargrey April 6, 2026 05:55
@nathan-stender
nathan-stender force-pushed the feat/global-locale-support branch from b4f38b3 to aedef7a Compare April 6, 2026 06:02
Change from generic Exception to AllotropeConversionError
to satisfy ruff B017 linting rule.

Co-Authored-By: Claude Opus 4.1 <noreply@anthropic.com>
@nathan-stender
nathan-stender force-pushed the feat/global-locale-support branch from aedef7a to 1a782c8 Compare April 6, 2026 06:05
…port

Replace pd.to_numeric, astype(float), and float() calls with try_float
utilities to enable locale-aware number parsing across all parsers.

Changes:
- roche_cedex_bioht: Replace to_num() with try_float_or_none + np.nan replacement
- luminex_xponent: Replace pd.to_numeric with try_float_or_none for analyte columns
- moldev_softmax_pro: Replace pd.to_numeric with try_float for index conversion
- thermo_skanit: Replace float() with try_float for wavelength parsing
- thermo_skanit: Replace astype(float) with try_float for column headers
- agilent_gen5: Replace astype(float) with try_float for wavelength parsing
- cytiva_biacore_t200_control: Replace astype(float) with try_float for sensorgram data
- thermo_fisher_genesys30: Replace astype(float) with try_float for absorbance data
- appbio_absolute_q: Replace astype(float) with try_float for fluorescence data

All parser tests pass with no changes to golden files.

Co-Authored-By: Claude Opus 4.1 <noreply@anthropic.com>
@nathan-stender nathan-stender changed the title Add global locale support for number parsing feat: Add global locale support for number parsing Apr 6, 2026
Add convenience function to simplify the common pattern of converting
pandas Series to list[float] using locale-aware parsing.

Changes:
- Add series_to_float_list() helper to utils/pandas.py
- Update 4 parsers to use the new helper:
  - cytiva_biacore_t200_control: sensorgram time and resonance data
  - thermo_fisher_genesys30: absorbance data
  - appbio_absolute_q: cycle index and fluorescence data
  - agilent_gen5: wavelength parsing

Replaces verbose list comprehensions with cleaner helper calls:
  Before: [try_float(str(v), "name") for v in series]
  After: series_to_float_list(series, "name")

Co-Authored-By: Claude Opus 4.1 <noreply@anthropic.com>
@nathan-stender
nathan-stender merged commit 4d9207a into main Apr 6, 2026
9 checks passed
@nathan-stender
nathan-stender deleted the feat/global-locale-support branch April 6, 2026 16:11
nathan-stender added a commit that referenced this pull request Apr 7, 2026
Resolves P1 TODO #3 from codebase audit by adding support for locale-specific
date formats (day-first vs month-first) using Babel's CLDR data.

## Problem
Previously, TimestampParser always used American date format (MM/DD/YYYY),
causing ambiguous dates like "01/02/2023" to parse incorrectly for European
users who expect DD/MM/YYYY format.

## Solution
- Use Babel CLDR data to determine if a locale uses day-first format
- Integrate with existing locale context (from PR #1165)
- ISO 8601 year-first formats (YYYY-MM-DD) always parse correctly
- Defaults to American format when no locale context is set

## Implementation
- Added `_should_use_day_first()` helper that:
  - Returns False for ISO 8601 year-first formats (YYYY-MM-DD)
  - Uses Babel to check locale's short date format pattern
  - Returns True if day appears before month in pattern
- `TimestampParser.parse()` now gets locale from context and passes
  `dayfirst` parameter to `dateutil.parser.parse()`
- Comprehensive test coverage (18 tests) for various locales and formats

## Testing
- All new tests pass (18 timestamp parser tests + 3 locale integration tests)
- No changes to existing behavior when locale not set
- Tested: en_US, en_GB, de_DE, fr_FR, es_ES, it_IT, pt_PT, nl_NL, ja_JP, zh_CN, ko_KR

Co-Authored-By: Claude Opus 4.1 <noreply@anthropic.com>
nathan-stender added a commit that referenced this pull request Apr 7, 2026
## Summary
Adds locale-aware timestamp parsing to resolve P1 TODO #3 from codebase
audit. Uses Babel's CLDR data to correctly parse ambiguous dates based
on locale (e.g., "01/02/2023" → January 2 in US, February 1 in Europe).

## Problem
Previously, `TimestampParser` always assumed American date format
(MM/DD/YYYY):
```python
# Line 23 in timestamp_parser.py (before)
# TODO: TimestampParser should support localization -- e.g., passing "dayfirst=True" to dateutil.parser.parse.
```

This caused incorrect parsing for international users:
- `"01/02/2023"` always parsed as January 2nd, never February 1st
- European/Asian date formats failed for ambiguous dates
- No way to specify locale-specific parsing behavior

## Solution
Integrated with existing locale context infrastructure (from PR #1165)
using **Babel CLDR** (industry standard):

### Key Changes
1. **`_should_use_day_first(time_str, locale_str)`** helper:
   - Detects ISO 8601 year-first (YYYY-MM-DD) → always month-before-day
   - Uses Babel to check locale's short date format pattern from CLDR
   - Returns `True` if day appears before month in pattern

2. **`TimestampParser.parse()`** enhancement:
   - Gets locale from context via `get_current_locale()`
   - Passes `dayfirst` parameter to `dateutil.parser.parse()`
   - Backward compatible: defaults to American format when no locale set

### How it works
```python
from allotropy.to_allotrope import allotrope_from_file

# American locale (month-first)
allotrope_from_file("data.txt", "VENDOR", locale="en_US")
# "01/02/2023" → 2023-01-02 (January 2nd)

# German locale (day-first)  
allotrope_from_file("data.txt", "VENDOR", locale="de_DE")
# "01/02/2023" → 2023-02-01 (February 1st)

# ISO format works everywhere
# "2023-01-02" → 2023-01-02 (always January 2nd)
```

## Babel CLDR Data
Uses Unicode CLDR via Babel - the authoritative source for locale data:
- **Day-first**: `en_GB`, `de_DE`, `fr_FR`, `es_ES`, `it_IT`, most of
Europe
- **Month-first**: `en_US` (only major English variant)
- **Year-first**: `ja_JP`, `zh_CN`, `ko_KR` (YMD format)

## Testing
Comprehensive test coverage (18 tests):
- ✅ ISO 8601 formats ignore locale (always YYYY-MM-DD)
- ✅ Ambiguous dates parse correctly by locale (`"01/02/2023"`)
- ✅ Unambiguous dates work everywhere (`"31/01/2023"`)
- ✅ Multiple European locales tested
- ✅ Asian year-first locales tested
- ✅ Backward compatible (no locale → American format)
- ✅ Real-world instrument date formats
- ✅ All 546 existing tests pass

## Impact
- **No breaking changes** - backward compatible
- **No golden file changes** - existing behavior preserved when locale
not set
- **Enables international support** - fixes dates for European/Asian
users
- **Industry standard** - uses Babel CLDR, same as browsers/ICU

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.1 <noreply@anthropic.com>
nathan-stender added a commit that referenced this pull request Apr 8, 2026
### Added

- Add locale-aware timestamp parsing using Babel CLDR data (#1167)
- Add global locale support for number parsing (#1165)
- Reduce Cytiva Biacore T200 Control memory usage with cycle streaming
(#1164)
- Cytiva T200 - Implement streaming decoder to reduce memory usage by
55% (#1163)

### Fixed

- Default to day-first format for invalid/unknown locales (#1168)
- Use immutable copy pattern for WellItem result attachment (#1166)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants