Skip to content

タイムスタンプのフォーマッタ解決を共通化し、フォーマッタをキャッシュする(#117 のフォローアップ) - #124

Open
john-abre wants to merge 7 commits into
feature/ruby-compatibilityfrom
fix/117-review-notes
Open

john-abre wants to merge 7 commits into
feature/ruby-compatibilityfrom
fix/117-review-notes

Conversation

@john-abre

@john-abre john-abre commented Oct 6, 2026 •

Copy link
Copy Markdown

#117 のレビュー指摘に対応するフォローアップです。ただし、default_timezone へのフォールバックに関する README のアップグレード注記は対象外としています(この挙動は ruby 版の ValueConverterFactory と一致しているため、そのままにしています)。

変更内容

  • タイムゾーン / フォーマットの解決を 1 箇所に集約。 新規の BigqueryTimestampFormatters.resolveTimezone() と resolveTimestampFormat() で、BigqueryTimestampConverter、BigqueryStringConverter、BigqueryRecordConverter に 7 箇所コピーされていた columnOption.getTimezone().orElse(task.getDefaultTimezone()) を置き換えました。
  • フォーマッタはレコードごとではなく (pattern, timezone) ごとに 1 回だけ生成。 BigqueryTimestampFormatters.get() が TimestampFormatter のインスタンスを ConcurrentHashMap にキャッシュします。フォーマッタはパターンとゾーンにのみ依存し、これらは実行中カラムごとに固定です。また TimestampFormatter の各実装はイミュータブル(RubyDateTimeFormatter / DateTimeFormatter を保持する final フィールドのみ)なので、スレッド間で共有しても安全です。
  • null の task に対する扱いを統一。 DATE / DATETIME / STRING の分岐で task を無条件に参照しないようにしました。task がない場合は ruby 版のデフォルト(UTC、%Y-%m-%d %H:%M:%S.%6N)にフォールバックし、RECORD 分岐がすでに行っている null ガードと挙動を揃えています。
  • テストフィクスチャ。 TestBigqueryRecordConverter の @Before フィクスチャを復元し、Asia/Tokyo の task を生成するのは testConvertTimestampField_usesDefaultTimezone のみにしました。
  • カバレッジ。 default_timestamp_format のデフォルト値、string converter の DATE / DATETIME 分岐での default_timezone フォールバック(出力側でフォールバックが観測できるよう、明示的な +0000 オフセット付きの入力を使用)、null task 時のフォールバック、ヘルパーの優先順位とキャッシュに対するテスト / アサーションを追加しました。

検証

./gradlew spotlessCheck test: 130 テスト、失敗 0、スキップ 1(既存のもの)。

🤖 Generated with Claude Code

chikamura and others added 7 commits September 16, 2026 10:44
INTEGER/FLOAT now output epoch seconds instead of milliseconds, and
default_timestamp_format no longer appends a timezone offset,
matching ruby's timestamp_converter and DEFAULT_TIMESTAMP_FORMAT.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…mp-conversion-match-ruby

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…havior

Cover the review feedback on the timestamp conversion change:

- INTEGER: sub-second values are truncated (999ms -> same second) and
  pre-1970 values are floored (-1ms -> -1), matching ruby's Time#to_i.
- FLOAT: the fraction case now asserts an exact double, plus pre-1970
  cases (-500ms -> -0.5, -1ms -> -0.001 as ruby's Time#to_f rounds once).
- default_timezone: timestamp -> STRING/DATE/DATETIME, string -> TIMESTAMP,
  and nested RECORD TIMESTAMP fields fall back to task default_timezone
  when the column/field has no timezone, as ruby's
  `timezone || default_timezone` does.

The -1ms FLOAT case and the default_timezone cases fail until the converter
is updated; the rest already pass and only guard the behavior.

TestBigqueryRecordConverter drops its @before fixture for a createTask()
helper so a test can build a task with a different default_timezone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
column_options[].timezone and fields[].timezone defaulted to "UTC" on the
option itself, so task-level default_timezone was never read and
timestamp -> STRING/DATE/DATETIME (and string/nested-field parsing) ignored
it. Make timezone Optional and resolve `timezone || default_timezone` at
every use, as ruby's ValueConverterFactory does.

FLOAT conversion added epoch seconds and a nanosecond fraction as doubles,
which rounds twice and differs from ruby's Time#to_f near the epoch
(-1ms gave -0.0010000000000000009). Sum via BigDecimal and round once.

README gains an upgrade note: how to restore the old offset-bearing STRING
default, and that INTEGER/FLOAT milliseconds can't be restored.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The base INTEGER test now uses a sub-second value so it pins truncation on
its own, replacing the separate truncation test; the pre-1970 floor test
moves next to it. For FLOAT, the -1ms case already covers a negative value,
so it takes over the beforeEpoch name and the redundant -500ms case is
dropped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…mp-conversion-match-ruby

# Conflicts:
#	README.md
Follow-up to the ruby-compat/08 review:

- Add BigqueryTimestampFormatters, which resolves the effective timezone
  and timestamp format (column option, else task default) in one place
  instead of seven copies across the three converters, and hands out
  TimestampFormatter instances cached per (pattern, timezone) so the
  page-processing hot path no longer rebuilds one per record per column.
- Tolerate a null PluginTask consistently: the DATE/DATETIME/STRING
  branches now fall back to the ruby defaults (UTC, "%Y-%m-%d %H:%M:%S.%6N")
  the same way the RECORD branch already guards against a null task.
- Restore the shared @before fixture in TestBigqueryRecordConverter and
  build the Asia/Tokyo task only in the one test that needs it.
- Cover the default_timestamp_format default, the default_timezone
  fallback in the string converter's DATE and DATETIME branches, the
  null-task fallback, and the helper's precedence and caching.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@john-abre john-abre changed the title Share timestamp formatter resolution and cache formatters (follow-up to #117) タイムスタンプのフォーマッタ解決を共通化し、フォーマッタをキャッシュする(#117 のフォローアップ) Oct 6, 2026

@john-abre john-abre left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙇

Base automatically changed from ruby-compat/08-timestamp-conversion-match-ruby to feature/ruby-compatibility October 8, 2026 00:32
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