Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions cpp/src/arrow/compute/api_scalar.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -353,7 +353,8 @@ static auto kStrftimeOptionsType = GetFunctionOptionsType<StrftimeOptions>(
DataMember("format", &StrftimeOptions::format));
static auto kStrptimeOptionsType = GetFunctionOptionsType<StrptimeOptions>(
DataMember("format", &StrptimeOptions::format),
DataMember("unit", &StrptimeOptions::unit));
DataMember("unit", &StrptimeOptions::unit),
DataMember("error_is_null", &StrptimeOptions::error_is_null));
static auto kStructFieldOptionsType = GetFunctionOptionsType<StructFieldOptions>(
DataMember("indices", &StructFieldOptions::indices));
static auto kTrimOptionsType = GetFunctionOptionsType<TrimOptions>(
Expand DownExpand Up@@ -544,11 +545,13 @@ StrftimeOptions::StrftimeOptions() : StrftimeOptions(kDefaultFormat) {}
constexpr char StrftimeOptions::kTypeName[];
constexpr const char* StrftimeOptions::kDefaultFormat;

StrptimeOptions::StrptimeOptions(std::string format, TimeUnit::type unit)
StrptimeOptions::StrptimeOptions(std::string format, TimeUnit::type unit,
bool error_is_null)
: FunctionOptions(internal::kStrptimeOptionsType),
format(std::move(format)),
unit(unit) {}
StrptimeOptions::StrptimeOptions() : StrptimeOptions("", TimeUnit::SECOND) {}
unit(unit),
error_is_null(error_is_null) {}
StrptimeOptions::StrptimeOptions() : StrptimeOptions("", TimeUnit::MICRO, false) {}
constexpr char StrptimeOptions::kTypeName[];

StructFieldOptions::StructFieldOptions(std::vector<int> indices)
Expand DownExpand Up@@ -822,6 +825,10 @@ Result<Datum> Strftime(const Datum& arg, StrftimeOptions options, ExecContext* c
return CallFunction("strftime", {arg}, &options, ctx);
}

Result<Datum> Strptime(const Datum& arg, StrptimeOptions options, ExecContext* ctx) {
return CallFunction("strptime", {arg}, &options, ctx);
}

Result<Datum> Week(const Datum& arg, WeekOptions options, ExecContext* ctx) {
return CallFunction("week", {arg}, &options, ctx);
}
Expand Down
23 changes: 22 additions & 1 deletion cpp/src/arrow/compute/api_scalar.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -267,12 +267,17 @@ class ARROW_EXPORT StructFieldOptions : public FunctionOptions {

class ARROW_EXPORT StrptimeOptions : public FunctionOptions {
public:
explicit StrptimeOptions(std::string format, TimeUnit::type unit);
explicit StrptimeOptions(std::string format, TimeUnit::type unit,
bool error_is_null = false);
StrptimeOptions();
static constexpr char const kTypeName[] = "StrptimeOptions";

/// The desired format string.
std::string format;
/// The desired time resolution
TimeUnit::type unit;
/// Return null on parsing errors if true or raise if false
bool error_is_null;
};

class ARROW_EXPORT StrftimeOptions : public FunctionOptions {
Expand DownExpand Up@@ -1398,6 +1403,22 @@ ARROW_EXPORT Result<Datum> Subsecond(const Datum& values, ExecContext* ctx = NUL
ARROW_EXPORT Result<Datum> Strftime(const Datum& values, StrftimeOptions options,
ExecContext* ctx = NULLPTR);

/// \brief Parse timestamps according to a format string
///
/// Return parsed timestamps according to the format string
/// `StrptimeOptions::format` at time resolution `Strftime::unit`. Parse errors are
/// raised depending on the `Strftime::error_is_null` setting.
///
/// \param[in] values input strings
/// \param[in] options for setting format string, unit and error_is_null
/// \param[in] ctx the function execution context, optional
/// \return the resulting datum
///
/// \since 8.0.0
/// \note API not yet finalized
ARROW_EXPORT Result<Datum> Strptime(const Datum& values, StrptimeOptions options,
ExecContext* ctx = NULLPTR);

/// \brief Converts timestamps from local timestamp without a timezone to a timestamp with
/// timezone, interpreting the local timestamp as being in the specified timezone for each
/// element of `values`
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/arrow/compute/exec/expression_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -737,7 +737,7 @@ TEST(Expression, ExecuteCall) {
])"));

ExpectExecute(call("strptime", {field_ref("a")},
compute::StrptimeOptions("%m/%d/%Y", TimeUnit::MICRO)),
compute::StrptimeOptions("%m/%d/%Y", TimeUnit::MICRO, true)),
ArrayFromJSON(struct_({field("a", utf8())}), R"([
{"a": "5/1/2020"},
{"a": null},
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/arrow/compute/function_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,7 +88,7 @@ TEST(FunctionOptions, Equality) {
options.emplace_back(new ExtractRegexOptions("pattern2"));
options.emplace_back(new SetLookupOptions(ArrayFromJSON(int64(), "[1, 2, 3, 4]")));
options.emplace_back(new SetLookupOptions(ArrayFromJSON(boolean(), "[true, false]")));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::MILLI));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::MILLI, true));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::NANO));
options.emplace_back(new StrftimeOptions("%Y-%m-%dT%H:%M:%SZ", "C"));
#ifndef _WIN32
Expand Down
74 changes: 0 additions & 74 deletions cpp/src/arrow/compute/kernels/scalar_string_ascii.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -2770,79 +2770,6 @@ void AddAsciiStringSplitRegex(FunctionRegistry* registry) {
}
#endif // ARROW_WITH_RE2

// ----------------------------------------------------------------------
// strptime string parsing

using StrptimeState = OptionsWrapper<StrptimeOptions>;

struct ParseStrptime {
explicit ParseStrptime(const StrptimeOptions& options)
: parser(TimestampParser::MakeStrptime(options.format)), unit(options.unit) {}

template <typename... Ignored>
int64_t Call(KernelContext*, util::string_view val, Status* st) const {
int64_t result = 0;
if (!(*parser)(val.data(), val.size(), unit, &result)) {
*st = Status::Invalid("Failed to parse string: '", val, "' as a scalar of type ",
TimestampType(unit).ToString());
}
return result;
}

std::shared_ptr<TimestampParser> parser;
TimeUnit::type unit;
};

template <typename InputType>
struct StrptimeExec {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
applicator::ScalarUnaryNotNullStateful<TimestampType, InputType, ParseStrptime>
kernel{ParseStrptime(StrptimeState::Get(ctx))};
return kernel.Exec(ctx, batch, out);
}
};

Result<ValueDescr> ResolveStrptimeOutput(KernelContext* ctx,
const std::vector<ValueDescr>&) {
if (!ctx->state()) {
return Status::Invalid("strptime does not provide default StrptimeOptions");
}
const StrptimeOptions& options = StrptimeState::Get(ctx);
// Check for use of %z or %Z
size_t cur = 0;
std::string zone = "";
while (cur < options.format.size() - 1) {
if (options.format[cur] == '%') {
if (options.format[cur + 1] == 'z') {
zone = "UTC";
break;
}
cur++;
}
cur++;
}
return ::arrow::timestamp(options.unit, zone);
}

const FunctionDoc strptime_doc(
"Parse timestamps",
("For each string in `strings`, parse it as a timestamp.\n"
"The timestamp unit and the expected string pattern must be given\n"
"in StrptimeOptions. Null inputs emit null. If a non-null string\n"
"fails parsing, an error is returned."),
{"strings"}, "StrptimeOptions", /*options_required=*/true);

void AddAsciiStringStrptime(FunctionRegistry* registry) {
auto func = std::make_shared<ScalarFunction>("strptime", Arity::Unary(), &strptime_doc);

OutputType out_ty(ResolveStrptimeOutput);
for (const auto& ty : StringTypes()) {
auto exec = GenerateVarBinaryToVarBinary<StrptimeExec>(ty);
DCHECK_OK(func->AddKernel({ty}, out_ty, std::move(exec), StrptimeState::Init));
}
DCHECK_OK(registry->AddFunction(std::move(func)));
}

// ----------------------------------------------------------------------
// Binary join

Expand DownExpand Up@@ -3518,7 +3445,6 @@ void RegisterScalarStringAscii(FunctionRegistry* registry) {
#ifdef ARROW_WITH_RE2
AddAsciiStringSplitRegex(registry);
#endif
AddAsciiStringStrptime(registry);
AddAsciiStringJoin(registry);
AddAsciiStringRepeat(registry);
}
Expand Down
34 changes: 27 additions & 7 deletions cpp/src/arrow/compute/kernels/scalar_string_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -1840,14 +1840,34 @@ TYPED_TEST(TestBaseBinaryKernels, ExtractRegexInvalid) {
#endif

TYPED_TEST(TestStringKernels, Strptime) {
std::string input1 = R"(["5/1/2020", null, "12/11/1900"])";
std::string output1 = R"(["2020-05-01", null, "1900-12-11"])";
StrptimeOptions options("%m/%d/%Y", TimeUnit::MICRO);
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO), output1, &options);
std::string input1 = R"(["5/1/2020", null, null, "12/13/1900", null])";
std::string input2 = R"(["5/1/2020", "12/13/1900"])";
std::string input3 = R"(["5/1/2020", "AA/BB/CCCC"])";
std::string input4 = R"(["5/1/2020", "AA/BB/CCCC", "AA/BB/CCCC", "AA/BB/CCCC", null])";
std::string input5 = R"(["5/1/2020 %z", null, null, "12/13/1900 %z", null])";
std::string output1 = R"(["2020-05-01", null, null, "1900-12-13", null])";
std::string output4 = R"(["2020-01-05", null, null, null, null])";
std::string output2 = R"(["2020-05-01", "1900-12-13"])";
std::string output3 = R"(["2020-05-01", null])";

StrptimeOptions options("%m/%d/%Y", TimeUnit::MICRO, /*error_is_null=*/true);
auto unit = timestamp(TimeUnit::MICRO);
this->CheckUnary("strptime", input1, unit, output1, &options);
this->CheckUnary("strptime", input2, unit, output2, &options);
this->CheckUnary("strptime", input3, unit, output3, &options);

options.format = "%d/%m/%Y";
this->CheckUnary("strptime", input4, unit, output4, &options);

input1 = R"(["5/1/2020 %z", null, "12/11/1900 %z"])";
options.format = "%m/%d/%Y %%z";
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO), output1, &options);
this->CheckUnary("strptime", input5, unit, output1, &options);

options.error_is_null = false;
this->CheckUnary("strptime", input5, unit, output1, &options);

EXPECT_RAISES_WITH_MESSAGE_THAT(
Invalid, testing::HasSubstr("Invalid: Failed to parse string: '5/1/2020'"),
Strptime(ArrayFromJSON(this->type(), input1), options));
}

TYPED_TEST(TestStringKernels, StrptimeZoneOffset) {
Expand All@@ -1859,7 +1879,7 @@ TYPED_TEST(TestStringKernels, StrptimeZoneOffset) {
std::string input1 = R"(["5/1/2020 +0100", null, "12/11/1900 -0130"])";
std::string output1 =
R"(["2020-04-30T23:00:00.000000", null, "1900-12-11T01:30:00.000000"])";
StrptimeOptions options("%m/%d/%Y %z", TimeUnit::MICRO);
StrptimeOptions options("%m/%d/%Y %z", TimeUnit::MICRO, /*error_is_null=*/true);
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO, "UTC"), output1,
&options);
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions cpp/src/arrow/compute/api_scalar.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -353,7 +353,8 @@ static auto kStrftimeOptionsType = GetFunctionOptionsType<StrftimeOptions>(
DataMember("format", &StrftimeOptions::format));
static auto kStrptimeOptionsType = GetFunctionOptionsType<StrptimeOptions>(
DataMember("format", &StrptimeOptions::format),
DataMember("unit", &StrptimeOptions::unit));
DataMember("unit", &StrptimeOptions::unit),
DataMember("error_is_null", &StrptimeOptions::error_is_null));
static auto kStructFieldOptionsType = GetFunctionOptionsType<StructFieldOptions>(
DataMember("indices", &StructFieldOptions::indices));
static auto kTrimOptionsType = GetFunctionOptionsType<TrimOptions>(
Expand DownExpand Up@@ -544,11 +545,13 @@ StrftimeOptions::StrftimeOptions() : StrftimeOptions(kDefaultFormat) {}
constexpr char StrftimeOptions::kTypeName[];
constexpr const char* StrftimeOptions::kDefaultFormat;

StrptimeOptions::StrptimeOptions(std::string format, TimeUnit::type unit)
StrptimeOptions::StrptimeOptions(std::string format, TimeUnit::type unit,
bool error_is_null)
: FunctionOptions(internal::kStrptimeOptionsType),
format(std::move(format)),
unit(unit) {}
StrptimeOptions::StrptimeOptions() : StrptimeOptions("", TimeUnit::SECOND) {}
unit(unit),
error_is_null(error_is_null) {}
StrptimeOptions::StrptimeOptions() : StrptimeOptions("", TimeUnit::MICRO, false) {}
constexpr char StrptimeOptions::kTypeName[];

StructFieldOptions::StructFieldOptions(std::vector<int> indices)
Expand DownExpand Up@@ -822,6 +825,10 @@ Result<Datum> Strftime(const Datum& arg, StrftimeOptions options, ExecContext* c
return CallFunction("strftime", {arg}, &options, ctx);
}

Result<Datum> Strptime(const Datum& arg, StrptimeOptions options, ExecContext* ctx) {
return CallFunction("strptime", {arg}, &options, ctx);
}

Result<Datum> Week(const Datum& arg, WeekOptions options, ExecContext* ctx) {
return CallFunction("week", {arg}, &options, ctx);
}
Expand Down
23 changes: 22 additions & 1 deletion cpp/src/arrow/compute/api_scalar.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -267,12 +267,17 @@ class ARROW_EXPORT StructFieldOptions : public FunctionOptions {

class ARROW_EXPORT StrptimeOptions : public FunctionOptions {
public:
explicit StrptimeOptions(std::string format, TimeUnit::type unit);
explicit StrptimeOptions(std::string format, TimeUnit::type unit,
bool error_is_null = false);
StrptimeOptions();
static constexpr char const kTypeName[] = "StrptimeOptions";

/// The desired format string.
std::string format;
/// The desired time resolution
TimeUnit::type unit;
/// Return null on parsing errors if true or raise if false
bool error_is_null;
};

class ARROW_EXPORT StrftimeOptions : public FunctionOptions {
Expand DownExpand Up@@ -1398,6 +1403,22 @@ ARROW_EXPORT Result<Datum> Subsecond(const Datum& values, ExecContext* ctx = NUL
ARROW_EXPORT Result<Datum> Strftime(const Datum& values, StrftimeOptions options,
ExecContext* ctx = NULLPTR);

/// \brief Parse timestamps according to a format string
///
/// Return parsed timestamps according to the format string
/// `StrptimeOptions::format` at time resolution `Strftime::unit`. Parse errors are
/// raised depending on the `Strftime::error_is_null` setting.
///
/// \param[in] values input strings
/// \param[in] options for setting format string, unit and error_is_null
/// \param[in] ctx the function execution context, optional
/// \return the resulting datum
///
/// \since 8.0.0
/// \note API not yet finalized
ARROW_EXPORT Result<Datum> Strptime(const Datum& values, StrptimeOptions options,
ExecContext* ctx = NULLPTR);

/// \brief Converts timestamps from local timestamp without a timezone to a timestamp with
/// timezone, interpreting the local timestamp as being in the specified timezone for each
/// element of `values`
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/arrow/compute/exec/expression_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -737,7 +737,7 @@ TEST(Expression, ExecuteCall) {
])"));

ExpectExecute(call("strptime", {field_ref("a")},
compute::StrptimeOptions("%m/%d/%Y", TimeUnit::MICRO)),
compute::StrptimeOptions("%m/%d/%Y", TimeUnit::MICRO, true)),
ArrayFromJSON(struct_({field("a", utf8())}), R"([
{"a": "5/1/2020"},
{"a": null},
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/arrow/compute/function_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,7 +88,7 @@ TEST(FunctionOptions, Equality) {
options.emplace_back(new ExtractRegexOptions("pattern2"));
options.emplace_back(new SetLookupOptions(ArrayFromJSON(int64(), "[1, 2, 3, 4]")));
options.emplace_back(new SetLookupOptions(ArrayFromJSON(boolean(), "[true, false]")));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::MILLI));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::MILLI, true));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::NANO));
options.emplace_back(new StrftimeOptions("%Y-%m-%dT%H:%M:%SZ", "C"));
#ifndef _WIN32
Expand Down
74 changes: 0 additions & 74 deletions cpp/src/arrow/compute/kernels/scalar_string_ascii.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -2770,79 +2770,6 @@ void AddAsciiStringSplitRegex(FunctionRegistry* registry) {
}
#endif // ARROW_WITH_RE2

// ----------------------------------------------------------------------
// strptime string parsing

using StrptimeState = OptionsWrapper<StrptimeOptions>;

struct ParseStrptime {
explicit ParseStrptime(const StrptimeOptions& options)
: parser(TimestampParser::MakeStrptime(options.format)), unit(options.unit) {}

template <typename... Ignored>
int64_t Call(KernelContext*, util::string_view val, Status* st) const {
int64_t result = 0;
if (!(*parser)(val.data(), val.size(), unit, &result)) {
*st = Status::Invalid("Failed to parse string: '", val, "' as a scalar of type ",
TimestampType(unit).ToString());
}
return result;
}

std::shared_ptr<TimestampParser> parser;
TimeUnit::type unit;
};

template <typename InputType>
struct StrptimeExec {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
applicator::ScalarUnaryNotNullStateful<TimestampType, InputType, ParseStrptime>
kernel{ParseStrptime(StrptimeState::Get(ctx))};
return kernel.Exec(ctx, batch, out);
}
};

Result<ValueDescr> ResolveStrptimeOutput(KernelContext* ctx,
const std::vector<ValueDescr>&) {
if (!ctx->state()) {
return Status::Invalid("strptime does not provide default StrptimeOptions");
}
const StrptimeOptions& options = StrptimeState::Get(ctx);
// Check for use of %z or %Z
size_t cur = 0;
std::string zone = "";
while (cur < options.format.size() - 1) {
if (options.format[cur] == '%') {
if (options.format[cur + 1] == 'z') {
zone = "UTC";
break;
}
cur++;
}
cur++;
}
return ::arrow::timestamp(options.unit, zone);
}

const FunctionDoc strptime_doc(
"Parse timestamps",
("For each string in `strings`, parse it as a timestamp.\n"
"The timestamp unit and the expected string pattern must be given\n"
"in StrptimeOptions. Null inputs emit null. If a non-null string\n"
"fails parsing, an error is returned."),
{"strings"}, "StrptimeOptions", /*options_required=*/true);

void AddAsciiStringStrptime(FunctionRegistry* registry) {
auto func = std::make_shared<ScalarFunction>("strptime", Arity::Unary(), &strptime_doc);

OutputType out_ty(ResolveStrptimeOutput);
for (const auto& ty : StringTypes()) {
auto exec = GenerateVarBinaryToVarBinary<StrptimeExec>(ty);
DCHECK_OK(func->AddKernel({ty}, out_ty, std::move(exec), StrptimeState::Init));
}
DCHECK_OK(registry->AddFunction(std::move(func)));
}

// ----------------------------------------------------------------------
// Binary join

Expand DownExpand Up@@ -3518,7 +3445,6 @@ void RegisterScalarStringAscii(FunctionRegistry* registry) {
#ifdef ARROW_WITH_RE2
AddAsciiStringSplitRegex(registry);
#endif
AddAsciiStringStrptime(registry);
AddAsciiStringJoin(registry);
AddAsciiStringRepeat(registry);
}
Expand Down
34 changes: 27 additions & 7 deletions cpp/src/arrow/compute/kernels/scalar_string_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -1840,14 +1840,34 @@ TYPED_TEST(TestBaseBinaryKernels, ExtractRegexInvalid) {
#endif

TYPED_TEST(TestStringKernels, Strptime) {
std::string input1 = R"(["5/1/2020", null, "12/11/1900"])";
std::string output1 = R"(["2020-05-01", null, "1900-12-11"])";
StrptimeOptions options("%m/%d/%Y", TimeUnit::MICRO);
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO), output1, &options);
std::string input1 = R"(["5/1/2020", null, null, "12/13/1900", null])";
std::string input2 = R"(["5/1/2020", "12/13/1900"])";
std::string input3 = R"(["5/1/2020", "AA/BB/CCCC"])";
std::string input4 = R"(["5/1/2020", "AA/BB/CCCC", "AA/BB/CCCC", "AA/BB/CCCC", null])";
std::string input5 = R"(["5/1/2020 %z", null, null, "12/13/1900 %z", null])";
std::string output1 = R"(["2020-05-01", null, null, "1900-12-13", null])";
std::string output4 = R"(["2020-01-05", null, null, null, null])";
std::string output2 = R"(["2020-05-01", "1900-12-13"])";
std::string output3 = R"(["2020-05-01", null])";

StrptimeOptions options("%m/%d/%Y", TimeUnit::MICRO, /*error_is_null=*/true);
auto unit = timestamp(TimeUnit::MICRO);
this->CheckUnary("strptime", input1, unit, output1, &options);
this->CheckUnary("strptime", input2, unit, output2, &options);
this->CheckUnary("strptime", input3, unit, output3, &options);

options.format = "%d/%m/%Y";
this->CheckUnary("strptime", input4, unit, output4, &options);

input1 = R"(["5/1/2020 %z", null, "12/11/1900 %z"])";
options.format = "%m/%d/%Y %%z";
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO), output1, &options);
this->CheckUnary("strptime", input5, unit, output1, &options);

options.error_is_null = false;
this->CheckUnary("strptime", input5, unit, output1, &options);

EXPECT_RAISES_WITH_MESSAGE_THAT(
Invalid, testing::HasSubstr("Invalid: Failed to parse string: '5/1/2020'"),
Strptime(ArrayFromJSON(this->type(), input1), options));
}

TYPED_TEST(TestStringKernels, StrptimeZoneOffset) {
Expand All@@ -1859,7 +1879,7 @@ TYPED_TEST(TestStringKernels, StrptimeZoneOffset) {
std::string input1 = R"(["5/1/2020 +0100", null, "12/11/1900 -0130"])";
std::string output1 =
R"(["2020-04-30T23:00:00.000000", null, "1900-12-11T01:30:00.000000"])";
StrptimeOptions options("%m/%d/%Y %z", TimeUnit::MICRO);
StrptimeOptions options("%m/%d/%Y %z", TimeUnit::MICRO, /*error_is_null=*/true);
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO, "UTC"), output1,
&options);
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions cpp/src/arrow/compute/api_scalar.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -353,7 +353,8 @@ static auto kStrftimeOptionsType = GetFunctionOptionsType<StrftimeOptions>(
DataMember("format", &StrftimeOptions::format));
static auto kStrptimeOptionsType = GetFunctionOptionsType<StrptimeOptions>(
DataMember("format", &StrptimeOptions::format),
DataMember("unit", &StrptimeOptions::unit));
DataMember("unit", &StrptimeOptions::unit),
DataMember("error_is_null", &StrptimeOptions::error_is_null));
static auto kStructFieldOptionsType = GetFunctionOptionsType<StructFieldOptions>(
DataMember("indices", &StructFieldOptions::indices));
static auto kTrimOptionsType = GetFunctionOptionsType<TrimOptions>(
Expand DownExpand Up@@ -544,11 +545,13 @@ StrftimeOptions::StrftimeOptions() : StrftimeOptions(kDefaultFormat) {}
constexpr char StrftimeOptions::kTypeName[];
constexpr const char* StrftimeOptions::kDefaultFormat;

StrptimeOptions::StrptimeOptions(std::string format, TimeUnit::type unit)
StrptimeOptions::StrptimeOptions(std::string format, TimeUnit::type unit,
bool error_is_null)
: FunctionOptions(internal::kStrptimeOptionsType),
format(std::move(format)),
unit(unit) {}
StrptimeOptions::StrptimeOptions() : StrptimeOptions("", TimeUnit::SECOND) {}
unit(unit),
error_is_null(error_is_null) {}
StrptimeOptions::StrptimeOptions() : StrptimeOptions("", TimeUnit::MICRO, false) {}
constexpr char StrptimeOptions::kTypeName[];

StructFieldOptions::StructFieldOptions(std::vector<int> indices)
Expand DownExpand Up@@ -822,6 +825,10 @@ Result<Datum> Strftime(const Datum& arg, StrftimeOptions options, ExecContext* c
return CallFunction("strftime", {arg}, &options, ctx);
}

Result<Datum> Strptime(const Datum& arg, StrptimeOptions options, ExecContext* ctx) {
return CallFunction("strptime", {arg}, &options, ctx);
}

Result<Datum> Week(const Datum& arg, WeekOptions options, ExecContext* ctx) {
return CallFunction("week", {arg}, &options, ctx);
}
Expand Down
23 changes: 22 additions & 1 deletion cpp/src/arrow/compute/api_scalar.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -267,12 +267,17 @@ class ARROW_EXPORT StructFieldOptions : public FunctionOptions {

class ARROW_EXPORT StrptimeOptions : public FunctionOptions {
public:
explicit StrptimeOptions(std::string format, TimeUnit::type unit);
explicit StrptimeOptions(std::string format, TimeUnit::type unit,
bool error_is_null = false);
StrptimeOptions();
static constexpr char const kTypeName[] = "StrptimeOptions";

/// The desired format string.
std::string format;
/// The desired time resolution
TimeUnit::type unit;
/// Return null on parsing errors if true or raise if false
bool error_is_null;
};

class ARROW_EXPORT StrftimeOptions : public FunctionOptions {
Expand DownExpand Up@@ -1398,6 +1403,22 @@ ARROW_EXPORT Result<Datum> Subsecond(const Datum& values, ExecContext* ctx = NUL
ARROW_EXPORT Result<Datum> Strftime(const Datum& values, StrftimeOptions options,
ExecContext* ctx = NULLPTR);

/// \brief Parse timestamps according to a format string
///
/// Return parsed timestamps according to the format string
/// `StrptimeOptions::format` at time resolution `Strftime::unit`. Parse errors are
/// raised depending on the `Strftime::error_is_null` setting.
///
/// \param[in] values input strings
/// \param[in] options for setting format string, unit and error_is_null
/// \param[in] ctx the function execution context, optional
/// \return the resulting datum
///
/// \since 8.0.0
/// \note API not yet finalized
ARROW_EXPORT Result<Datum> Strptime(const Datum& values, StrptimeOptions options,
ExecContext* ctx = NULLPTR);

/// \brief Converts timestamps from local timestamp without a timezone to a timestamp with
/// timezone, interpreting the local timestamp as being in the specified timezone for each
/// element of `values`
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/arrow/compute/exec/expression_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -737,7 +737,7 @@ TEST(Expression, ExecuteCall) {
])"));

ExpectExecute(call("strptime", {field_ref("a")},
compute::StrptimeOptions("%m/%d/%Y", TimeUnit::MICRO)),
compute::StrptimeOptions("%m/%d/%Y", TimeUnit::MICRO, true)),
ArrayFromJSON(struct_({field("a", utf8())}), R"([
{"a": "5/1/2020"},
{"a": null},
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/arrow/compute/function_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,7 +88,7 @@ TEST(FunctionOptions, Equality) {
options.emplace_back(new ExtractRegexOptions("pattern2"));
options.emplace_back(new SetLookupOptions(ArrayFromJSON(int64(), "[1, 2, 3, 4]")));
options.emplace_back(new SetLookupOptions(ArrayFromJSON(boolean(), "[true, false]")));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::MILLI));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::MILLI, true));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::NANO));
options.emplace_back(new StrftimeOptions("%Y-%m-%dT%H:%M:%SZ", "C"));
#ifndef _WIN32
Expand Down
74 changes: 0 additions & 74 deletions cpp/src/arrow/compute/kernels/scalar_string_ascii.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -2770,79 +2770,6 @@ void AddAsciiStringSplitRegex(FunctionRegistry* registry) {
}
#endif // ARROW_WITH_RE2

// ----------------------------------------------------------------------
// strptime string parsing

using StrptimeState = OptionsWrapper<StrptimeOptions>;

struct ParseStrptime {
explicit ParseStrptime(const StrptimeOptions& options)
: parser(TimestampParser::MakeStrptime(options.format)), unit(options.unit) {}

template <typename... Ignored>
int64_t Call(KernelContext*, util::string_view val, Status* st) const {
int64_t result = 0;
if (!(*parser)(val.data(), val.size(), unit, &result)) {
*st = Status::Invalid("Failed to parse string: '", val, "' as a scalar of type ",
TimestampType(unit).ToString());
}
return result;
}

std::shared_ptr<TimestampParser> parser;
TimeUnit::type unit;
};

template <typename InputType>
struct StrptimeExec {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
applicator::ScalarUnaryNotNullStateful<TimestampType, InputType, ParseStrptime>
kernel{ParseStrptime(StrptimeState::Get(ctx))};
return kernel.Exec(ctx, batch, out);
}
};

Result<ValueDescr> ResolveStrptimeOutput(KernelContext* ctx,
const std::vector<ValueDescr>&) {
if (!ctx->state()) {
return Status::Invalid("strptime does not provide default StrptimeOptions");
}
const StrptimeOptions& options = StrptimeState::Get(ctx);
// Check for use of %z or %Z
size_t cur = 0;
std::string zone = "";
while (cur < options.format.size() - 1) {
if (options.format[cur] == '%') {
if (options.format[cur + 1] == 'z') {
zone = "UTC";
break;
}
cur++;
}
cur++;
}
return ::arrow::timestamp(options.unit, zone);
}

const FunctionDoc strptime_doc(
"Parse timestamps",
("For each string in `strings`, parse it as a timestamp.\n"
"The timestamp unit and the expected string pattern must be given\n"
"in StrptimeOptions. Null inputs emit null. If a non-null string\n"
"fails parsing, an error is returned."),
{"strings"}, "StrptimeOptions", /*options_required=*/true);

void AddAsciiStringStrptime(FunctionRegistry* registry) {
auto func = std::make_shared<ScalarFunction>("strptime", Arity::Unary(), &strptime_doc);

OutputType out_ty(ResolveStrptimeOutput);
for (const auto& ty : StringTypes()) {
auto exec = GenerateVarBinaryToVarBinary<StrptimeExec>(ty);
DCHECK_OK(func->AddKernel({ty}, out_ty, std::move(exec), StrptimeState::Init));
}
DCHECK_OK(registry->AddFunction(std::move(func)));
}

// ----------------------------------------------------------------------
// Binary join

Expand DownExpand Up@@ -3518,7 +3445,6 @@ void RegisterScalarStringAscii(FunctionRegistry* registry) {
#ifdef ARROW_WITH_RE2
AddAsciiStringSplitRegex(registry);
#endif
AddAsciiStringStrptime(registry);
AddAsciiStringJoin(registry);
AddAsciiStringRepeat(registry);
}
Expand Down
34 changes: 27 additions & 7 deletions cpp/src/arrow/compute/kernels/scalar_string_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -1840,14 +1840,34 @@ TYPED_TEST(TestBaseBinaryKernels, ExtractRegexInvalid) {
#endif

TYPED_TEST(TestStringKernels, Strptime) {
std::string input1 = R"(["5/1/2020", null, "12/11/1900"])";
std::string output1 = R"(["2020-05-01", null, "1900-12-11"])";
StrptimeOptions options("%m/%d/%Y", TimeUnit::MICRO);
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO), output1, &options);
std::string input1 = R"(["5/1/2020", null, null, "12/13/1900", null])";
std::string input2 = R"(["5/1/2020", "12/13/1900"])";
std::string input3 = R"(["5/1/2020", "AA/BB/CCCC"])";
std::string input4 = R"(["5/1/2020", "AA/BB/CCCC", "AA/BB/CCCC", "AA/BB/CCCC", null])";
std::string input5 = R"(["5/1/2020 %z", null, null, "12/13/1900 %z", null])";
std::string output1 = R"(["2020-05-01", null, null, "1900-12-13", null])";
std::string output4 = R"(["2020-01-05", null, null, null, null])";
std::string output2 = R"(["2020-05-01", "1900-12-13"])";
std::string output3 = R"(["2020-05-01", null])";

StrptimeOptions options("%m/%d/%Y", TimeUnit::MICRO, /*error_is_null=*/true);
auto unit = timestamp(TimeUnit::MICRO);
this->CheckUnary("strptime", input1, unit, output1, &options);
this->CheckUnary("strptime", input2, unit, output2, &options);
this->CheckUnary("strptime", input3, unit, output3, &options);

options.format = "%d/%m/%Y";
this->CheckUnary("strptime", input4, unit, output4, &options);

input1 = R"(["5/1/2020 %z", null, "12/11/1900 %z"])";
options.format = "%m/%d/%Y %%z";
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO), output1, &options);
this->CheckUnary("strptime", input5, unit, output1, &options);

options.error_is_null = false;
this->CheckUnary("strptime", input5, unit, output1, &options);

EXPECT_RAISES_WITH_MESSAGE_THAT(
Invalid, testing::HasSubstr("Invalid: Failed to parse string: '5/1/2020'"),
Strptime(ArrayFromJSON(this->type(), input1), options));
}

TYPED_TEST(TestStringKernels, StrptimeZoneOffset) {
Expand All@@ -1859,7 +1879,7 @@ TYPED_TEST(TestStringKernels, StrptimeZoneOffset) {
std::string input1 = R"(["5/1/2020 +0100", null, "12/11/1900 -0130"])";
std::string output1 =
R"(["2020-04-30T23:00:00.000000", null, "1900-12-11T01:30:00.000000"])";
StrptimeOptions options("%m/%d/%Y %z", TimeUnit::MICRO);
StrptimeOptions options("%m/%d/%Y %z", TimeUnit::MICRO, /*error_is_null=*/true);
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO, "UTC"), output1,
&options);
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions cpp/src/arrow/compute/api_scalar.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -353,7 +353,8 @@ static auto kStrftimeOptionsType = GetFunctionOptionsType<StrftimeOptions>(
DataMember("format", &StrftimeOptions::format));
static auto kStrptimeOptionsType = GetFunctionOptionsType<StrptimeOptions>(
DataMember("format", &StrptimeOptions::format),
DataMember("unit", &StrptimeOptions::unit));
DataMember("unit", &StrptimeOptions::unit),
DataMember("error_is_null", &StrptimeOptions::error_is_null));
static auto kStructFieldOptionsType = GetFunctionOptionsType<StructFieldOptions>(
DataMember("indices", &StructFieldOptions::indices));
static auto kTrimOptionsType = GetFunctionOptionsType<TrimOptions>(
Expand DownExpand Up@@ -544,11 +545,13 @@ StrftimeOptions::StrftimeOptions() : StrftimeOptions(kDefaultFormat) {}
constexpr char StrftimeOptions::kTypeName[];
constexpr const char* StrftimeOptions::kDefaultFormat;

StrptimeOptions::StrptimeOptions(std::string format, TimeUnit::type unit)
StrptimeOptions::StrptimeOptions(std::string format, TimeUnit::type unit,
bool error_is_null)
: FunctionOptions(internal::kStrptimeOptionsType),
format(std::move(format)),
unit(unit) {}
StrptimeOptions::StrptimeOptions() : StrptimeOptions("", TimeUnit::SECOND) {}
unit(unit),
error_is_null(error_is_null) {}
StrptimeOptions::StrptimeOptions() : StrptimeOptions("", TimeUnit::MICRO, false) {}
constexpr char StrptimeOptions::kTypeName[];

StructFieldOptions::StructFieldOptions(std::vector<int> indices)
Expand DownExpand Up@@ -822,6 +825,10 @@ Result<Datum> Strftime(const Datum& arg, StrftimeOptions options, ExecContext* c
return CallFunction("strftime", {arg}, &options, ctx);
}

Result<Datum> Strptime(const Datum& arg, StrptimeOptions options, ExecContext* ctx) {
return CallFunction("strptime", {arg}, &options, ctx);
}

Result<Datum> Week(const Datum& arg, WeekOptions options, ExecContext* ctx) {
return CallFunction("week", {arg}, &options, ctx);
}
Expand Down
23 changes: 22 additions & 1 deletion cpp/src/arrow/compute/api_scalar.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -267,12 +267,17 @@ class ARROW_EXPORT StructFieldOptions : public FunctionOptions {

class ARROW_EXPORT StrptimeOptions : public FunctionOptions {
public:
explicit StrptimeOptions(std::string format, TimeUnit::type unit);
explicit StrptimeOptions(std::string format, TimeUnit::type unit,
bool error_is_null = false);
StrptimeOptions();
static constexpr char const kTypeName[] = "StrptimeOptions";

/// The desired format string.
std::string format;
/// The desired time resolution
TimeUnit::type unit;
/// Return null on parsing errors if true or raise if false
bool error_is_null;
};

class ARROW_EXPORT StrftimeOptions : public FunctionOptions {
Expand DownExpand Up@@ -1398,6 +1403,22 @@ ARROW_EXPORT Result<Datum> Subsecond(const Datum& values, ExecContext* ctx = NUL
ARROW_EXPORT Result<Datum> Strftime(const Datum& values, StrftimeOptions options,
ExecContext* ctx = NULLPTR);

/// \brief Parse timestamps according to a format string
///
/// Return parsed timestamps according to the format string
/// `StrptimeOptions::format` at time resolution `Strftime::unit`. Parse errors are
/// raised depending on the `Strftime::error_is_null` setting.
///
/// \param[in] values input strings
/// \param[in] options for setting format string, unit and error_is_null
/// \param[in] ctx the function execution context, optional
/// \return the resulting datum
///
/// \since 8.0.0
/// \note API not yet finalized
ARROW_EXPORT Result<Datum> Strptime(const Datum& values, StrptimeOptions options,
ExecContext* ctx = NULLPTR);

/// \brief Converts timestamps from local timestamp without a timezone to a timestamp with
/// timezone, interpreting the local timestamp as being in the specified timezone for each
/// element of `values`
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/arrow/compute/exec/expression_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -737,7 +737,7 @@ TEST(Expression, ExecuteCall) {
])"));

ExpectExecute(call("strptime", {field_ref("a")},
compute::StrptimeOptions("%m/%d/%Y", TimeUnit::MICRO)),
compute::StrptimeOptions("%m/%d/%Y", TimeUnit::MICRO, true)),
ArrayFromJSON(struct_({field("a", utf8())}), R"([
{"a": "5/1/2020"},
{"a": null},
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/arrow/compute/function_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,7 +88,7 @@ TEST(FunctionOptions, Equality) {
options.emplace_back(new ExtractRegexOptions("pattern2"));
options.emplace_back(new SetLookupOptions(ArrayFromJSON(int64(), "[1, 2, 3, 4]")));
options.emplace_back(new SetLookupOptions(ArrayFromJSON(boolean(), "[true, false]")));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::MILLI));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::MILLI, true));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::NANO));
options.emplace_back(new StrftimeOptions("%Y-%m-%dT%H:%M:%SZ", "C"));
#ifndef _WIN32
Expand Down
74 changes: 0 additions & 74 deletions cpp/src/arrow/compute/kernels/scalar_string_ascii.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -2770,79 +2770,6 @@ void AddAsciiStringSplitRegex(FunctionRegistry* registry) {
}
#endif // ARROW_WITH_RE2

// ----------------------------------------------------------------------
// strptime string parsing

using StrptimeState = OptionsWrapper<StrptimeOptions>;

struct ParseStrptime {
explicit ParseStrptime(const StrptimeOptions& options)
: parser(TimestampParser::MakeStrptime(options.format)), unit(options.unit) {}

template <typename... Ignored>
int64_t Call(KernelContext*, util::string_view val, Status* st) const {
int64_t result = 0;
if (!(*parser)(val.data(), val.size(), unit, &result)) {
*st = Status::Invalid("Failed to parse string: '", val, "' as a scalar of type ",
TimestampType(unit).ToString());
}
return result;
}

std::shared_ptr<TimestampParser> parser;
TimeUnit::type unit;
};

template <typename InputType>
struct StrptimeExec {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
applicator::ScalarUnaryNotNullStateful<TimestampType, InputType, ParseStrptime>
kernel{ParseStrptime(StrptimeState::Get(ctx))};
return kernel.Exec(ctx, batch, out);
}
};

Result<ValueDescr> ResolveStrptimeOutput(KernelContext* ctx,
const std::vector<ValueDescr>&) {
if (!ctx->state()) {
return Status::Invalid("strptime does not provide default StrptimeOptions");
}
const StrptimeOptions& options = StrptimeState::Get(ctx);
// Check for use of %z or %Z
size_t cur = 0;
std::string zone = "";
while (cur < options.format.size() - 1) {
if (options.format[cur] == '%') {
if (options.format[cur + 1] == 'z') {
zone = "UTC";
break;
}
cur++;
}
cur++;
}
return ::arrow::timestamp(options.unit, zone);
}

const FunctionDoc strptime_doc(
"Parse timestamps",
("For each string in `strings`, parse it as a timestamp.\n"
"The timestamp unit and the expected string pattern must be given\n"
"in StrptimeOptions. Null inputs emit null. If a non-null string\n"
"fails parsing, an error is returned."),
{"strings"}, "StrptimeOptions", /*options_required=*/true);

void AddAsciiStringStrptime(FunctionRegistry* registry) {
auto func = std::make_shared<ScalarFunction>("strptime", Arity::Unary(), &strptime_doc);

OutputType out_ty(ResolveStrptimeOutput);
for (const auto& ty : StringTypes()) {
auto exec = GenerateVarBinaryToVarBinary<StrptimeExec>(ty);
DCHECK_OK(func->AddKernel({ty}, out_ty, std::move(exec), StrptimeState::Init));
}
DCHECK_OK(registry->AddFunction(std::move(func)));
}

// ----------------------------------------------------------------------
// Binary join

Expand DownExpand Up@@ -3518,7 +3445,6 @@ void RegisterScalarStringAscii(FunctionRegistry* registry) {
#ifdef ARROW_WITH_RE2
AddAsciiStringSplitRegex(registry);
#endif
AddAsciiStringStrptime(registry);
AddAsciiStringJoin(registry);
AddAsciiStringRepeat(registry);
}
Expand Down
34 changes: 27 additions & 7 deletions cpp/src/arrow/compute/kernels/scalar_string_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -1840,14 +1840,34 @@ TYPED_TEST(TestBaseBinaryKernels, ExtractRegexInvalid) {
#endif

TYPED_TEST(TestStringKernels, Strptime) {
std::string input1 = R"(["5/1/2020", null, "12/11/1900"])";
std::string output1 = R"(["2020-05-01", null, "1900-12-11"])";
StrptimeOptions options("%m/%d/%Y", TimeUnit::MICRO);
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO), output1, &options);
std::string input1 = R"(["5/1/2020", null, null, "12/13/1900", null])";
std::string input2 = R"(["5/1/2020", "12/13/1900"])";
std::string input3 = R"(["5/1/2020", "AA/BB/CCCC"])";
std::string input4 = R"(["5/1/2020", "AA/BB/CCCC", "AA/BB/CCCC", "AA/BB/CCCC", null])";
std::string input5 = R"(["5/1/2020 %z", null, null, "12/13/1900 %z", null])";
std::string output1 = R"(["2020-05-01", null, null, "1900-12-13", null])";
std::string output4 = R"(["2020-01-05", null, null, null, null])";
std::string output2 = R"(["2020-05-01", "1900-12-13"])";
std::string output3 = R"(["2020-05-01", null])";

StrptimeOptions options("%m/%d/%Y", TimeUnit::MICRO, /*error_is_null=*/true);
auto unit = timestamp(TimeUnit::MICRO);
this->CheckUnary("strptime", input1, unit, output1, &options);
this->CheckUnary("strptime", input2, unit, output2, &options);
this->CheckUnary("strptime", input3, unit, output3, &options);

options.format = "%d/%m/%Y";
this->CheckUnary("strptime", input4, unit, output4, &options);

input1 = R"(["5/1/2020 %z", null, "12/11/1900 %z"])";
options.format = "%m/%d/%Y %%z";
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO), output1, &options);
this->CheckUnary("strptime", input5, unit, output1, &options);

options.error_is_null = false;
this->CheckUnary("strptime", input5, unit, output1, &options);

EXPECT_RAISES_WITH_MESSAGE_THAT(
Invalid, testing::HasSubstr("Invalid: Failed to parse string: '5/1/2020'"),
Strptime(ArrayFromJSON(this->type(), input1), options));
}

TYPED_TEST(TestStringKernels, StrptimeZoneOffset) {
Expand All@@ -1859,7 +1879,7 @@ TYPED_TEST(TestStringKernels, StrptimeZoneOffset) {
std::string input1 = R"(["5/1/2020 +0100", null, "12/11/1900 -0130"])";
std::string output1 =
R"(["2020-04-30T23:00:00.000000", null, "1900-12-11T01:30:00.000000"])";
StrptimeOptions options("%m/%d/%Y %z", TimeUnit::MICRO);
StrptimeOptions options("%m/%d/%Y %z", TimeUnit::MICRO, /*error_is_null=*/true);
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO, "UTC"), output1,
&options);
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions cpp/src/arrow/compute/api_scalar.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -353,7 +353,8 @@ static auto kStrftimeOptionsType = GetFunctionOptionsType<StrftimeOptions>(
DataMember("format", &StrftimeOptions::format));
static auto kStrptimeOptionsType = GetFunctionOptionsType<StrptimeOptions>(
DataMember("format", &StrptimeOptions::format),
DataMember("unit", &StrptimeOptions::unit));
DataMember("unit", &StrptimeOptions::unit),
DataMember("error_is_null", &StrptimeOptions::error_is_null));
static auto kStructFieldOptionsType = GetFunctionOptionsType<StructFieldOptions>(
DataMember("indices", &StructFieldOptions::indices));
static auto kTrimOptionsType = GetFunctionOptionsType<TrimOptions>(
Expand DownExpand Up@@ -544,11 +545,13 @@ StrftimeOptions::StrftimeOptions() : StrftimeOptions(kDefaultFormat) {}
constexpr char StrftimeOptions::kTypeName[];
constexpr const char* StrftimeOptions::kDefaultFormat;

StrptimeOptions::StrptimeOptions(std::string format, TimeUnit::type unit)
StrptimeOptions::StrptimeOptions(std::string format, TimeUnit::type unit,
bool error_is_null)
: FunctionOptions(internal::kStrptimeOptionsType),
format(std::move(format)),
unit(unit) {}
StrptimeOptions::StrptimeOptions() : StrptimeOptions("", TimeUnit::SECOND) {}
unit(unit),
error_is_null(error_is_null) {}
StrptimeOptions::StrptimeOptions() : StrptimeOptions("", TimeUnit::MICRO, false) {}
constexpr char StrptimeOptions::kTypeName[];

StructFieldOptions::StructFieldOptions(std::vector<int> indices)
Expand DownExpand Up@@ -822,6 +825,10 @@ Result<Datum> Strftime(const Datum& arg, StrftimeOptions options, ExecContext* c
return CallFunction("strftime", {arg}, &options, ctx);
}

Result<Datum> Strptime(const Datum& arg, StrptimeOptions options, ExecContext* ctx) {
return CallFunction("strptime", {arg}, &options, ctx);
}

Result<Datum> Week(const Datum& arg, WeekOptions options, ExecContext* ctx) {
return CallFunction("week", {arg}, &options, ctx);
}
Expand Down
23 changes: 22 additions & 1 deletion cpp/src/arrow/compute/api_scalar.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -267,12 +267,17 @@ class ARROW_EXPORT StructFieldOptions : public FunctionOptions {

class ARROW_EXPORT StrptimeOptions : public FunctionOptions {
public:
explicit StrptimeOptions(std::string format, TimeUnit::type unit);
explicit StrptimeOptions(std::string format, TimeUnit::type unit,
bool error_is_null = false);
StrptimeOptions();
static constexpr char const kTypeName[] = "StrptimeOptions";

/// The desired format string.
std::string format;
/// The desired time resolution
TimeUnit::type unit;
/// Return null on parsing errors if true or raise if false
bool error_is_null;
};

class ARROW_EXPORT StrftimeOptions : public FunctionOptions {
Expand DownExpand Up@@ -1398,6 +1403,22 @@ ARROW_EXPORT Result<Datum> Subsecond(const Datum& values, ExecContext* ctx = NUL
ARROW_EXPORT Result<Datum> Strftime(const Datum& values, StrftimeOptions options,
ExecContext* ctx = NULLPTR);

/// \brief Parse timestamps according to a format string
///
/// Return parsed timestamps according to the format string
/// `StrptimeOptions::format` at time resolution `Strftime::unit`. Parse errors are
/// raised depending on the `Strftime::error_is_null` setting.
///
/// \param[in] values input strings
/// \param[in] options for setting format string, unit and error_is_null
/// \param[in] ctx the function execution context, optional
/// \return the resulting datum
///
/// \since 8.0.0
/// \note API not yet finalized
ARROW_EXPORT Result<Datum> Strptime(const Datum& values, StrptimeOptions options,
ExecContext* ctx = NULLPTR);

/// \brief Converts timestamps from local timestamp without a timezone to a timestamp with
/// timezone, interpreting the local timestamp as being in the specified timezone for each
/// element of `values`
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/arrow/compute/exec/expression_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -737,7 +737,7 @@ TEST(Expression, ExecuteCall) {
])"));

ExpectExecute(call("strptime", {field_ref("a")},
compute::StrptimeOptions("%m/%d/%Y", TimeUnit::MICRO)),
compute::StrptimeOptions("%m/%d/%Y", TimeUnit::MICRO, true)),
ArrayFromJSON(struct_({field("a", utf8())}), R"([
{"a": "5/1/2020"},
{"a": null},
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/arrow/compute/function_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,7 +88,7 @@ TEST(FunctionOptions, Equality) {
options.emplace_back(new ExtractRegexOptions("pattern2"));
options.emplace_back(new SetLookupOptions(ArrayFromJSON(int64(), "[1, 2, 3, 4]")));
options.emplace_back(new SetLookupOptions(ArrayFromJSON(boolean(), "[true, false]")));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::MILLI));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::MILLI, true));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::NANO));
options.emplace_back(new StrftimeOptions("%Y-%m-%dT%H:%M:%SZ", "C"));
#ifndef _WIN32
Expand Down
74 changes: 0 additions & 74 deletions cpp/src/arrow/compute/kernels/scalar_string_ascii.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -2770,79 +2770,6 @@ void AddAsciiStringSplitRegex(FunctionRegistry* registry) {
}
#endif // ARROW_WITH_RE2

// ----------------------------------------------------------------------
// strptime string parsing

using StrptimeState = OptionsWrapper<StrptimeOptions>;

struct ParseStrptime {
explicit ParseStrptime(const StrptimeOptions& options)
: parser(TimestampParser::MakeStrptime(options.format)), unit(options.unit) {}

template <typename... Ignored>
int64_t Call(KernelContext*, util::string_view val, Status* st) const {
int64_t result = 0;
if (!(*parser)(val.data(), val.size(), unit, &result)) {
*st = Status::Invalid("Failed to parse string: '", val, "' as a scalar of type ",
TimestampType(unit).ToString());
}
return result;
}

std::shared_ptr<TimestampParser> parser;
TimeUnit::type unit;
};

template <typename InputType>
struct StrptimeExec {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
applicator::ScalarUnaryNotNullStateful<TimestampType, InputType, ParseStrptime>
kernel{ParseStrptime(StrptimeState::Get(ctx))};
return kernel.Exec(ctx, batch, out);
}
};

Result<ValueDescr> ResolveStrptimeOutput(KernelContext* ctx,
const std::vector<ValueDescr>&) {
if (!ctx->state()) {
return Status::Invalid("strptime does not provide default StrptimeOptions");
}
const StrptimeOptions& options = StrptimeState::Get(ctx);
// Check for use of %z or %Z
size_t cur = 0;
std::string zone = "";
while (cur < options.format.size() - 1) {
if (options.format[cur] == '%') {
if (options.format[cur + 1] == 'z') {
zone = "UTC";
break;
}
cur++;
}
cur++;
}
return ::arrow::timestamp(options.unit, zone);
}

const FunctionDoc strptime_doc(
"Parse timestamps",
("For each string in `strings`, parse it as a timestamp.\n"
"The timestamp unit and the expected string pattern must be given\n"
"in StrptimeOptions. Null inputs emit null. If a non-null string\n"
"fails parsing, an error is returned."),
{"strings"}, "StrptimeOptions", /*options_required=*/true);

void AddAsciiStringStrptime(FunctionRegistry* registry) {
auto func = std::make_shared<ScalarFunction>("strptime", Arity::Unary(), &strptime_doc);

OutputType out_ty(ResolveStrptimeOutput);
for (const auto& ty : StringTypes()) {
auto exec = GenerateVarBinaryToVarBinary<StrptimeExec>(ty);
DCHECK_OK(func->AddKernel({ty}, out_ty, std::move(exec), StrptimeState::Init));
}
DCHECK_OK(registry->AddFunction(std::move(func)));
}

// ----------------------------------------------------------------------
// Binary join

Expand DownExpand Up@@ -3518,7 +3445,6 @@ void RegisterScalarStringAscii(FunctionRegistry* registry) {
#ifdef ARROW_WITH_RE2
AddAsciiStringSplitRegex(registry);
#endif
AddAsciiStringStrptime(registry);
AddAsciiStringJoin(registry);
AddAsciiStringRepeat(registry);
}
Expand Down
34 changes: 27 additions & 7 deletions cpp/src/arrow/compute/kernels/scalar_string_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -1840,14 +1840,34 @@ TYPED_TEST(TestBaseBinaryKernels, ExtractRegexInvalid) {
#endif

TYPED_TEST(TestStringKernels, Strptime) {
std::string input1 = R"(["5/1/2020", null, "12/11/1900"])";
std::string output1 = R"(["2020-05-01", null, "1900-12-11"])";
StrptimeOptions options("%m/%d/%Y", TimeUnit::MICRO);
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO), output1, &options);
std::string input1 = R"(["5/1/2020", null, null, "12/13/1900", null])";
std::string input2 = R"(["5/1/2020", "12/13/1900"])";
std::string input3 = R"(["5/1/2020", "AA/BB/CCCC"])";
std::string input4 = R"(["5/1/2020", "AA/BB/CCCC", "AA/BB/CCCC", "AA/BB/CCCC", null])";
std::string input5 = R"(["5/1/2020 %z", null, null, "12/13/1900 %z", null])";
std::string output1 = R"(["2020-05-01", null, null, "1900-12-13", null])";
std::string output4 = R"(["2020-01-05", null, null, null, null])";
std::string output2 = R"(["2020-05-01", "1900-12-13"])";
std::string output3 = R"(["2020-05-01", null])";

StrptimeOptions options("%m/%d/%Y", TimeUnit::MICRO, /*error_is_null=*/true);
auto unit = timestamp(TimeUnit::MICRO);
this->CheckUnary("strptime", input1, unit, output1, &options);
this->CheckUnary("strptime", input2, unit, output2, &options);
this->CheckUnary("strptime", input3, unit, output3, &options);

options.format = "%d/%m/%Y";
this->CheckUnary("strptime", input4, unit, output4, &options);

input1 = R"(["5/1/2020 %z", null, "12/11/1900 %z"])";
options.format = "%m/%d/%Y %%z";
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO), output1, &options);
this->CheckUnary("strptime", input5, unit, output1, &options);

options.error_is_null = false;
this->CheckUnary("strptime", input5, unit, output1, &options);

EXPECT_RAISES_WITH_MESSAGE_THAT(
Invalid, testing::HasSubstr("Invalid: Failed to parse string: '5/1/2020'"),
Strptime(ArrayFromJSON(this->type(), input1), options));
}

TYPED_TEST(TestStringKernels, StrptimeZoneOffset) {
Expand All@@ -1859,7 +1879,7 @@ TYPED_TEST(TestStringKernels, StrptimeZoneOffset) {
std::string input1 = R"(["5/1/2020 +0100", null, "12/11/1900 -0130"])";
std::string output1 =
R"(["2020-04-30T23:00:00.000000", null, "1900-12-11T01:30:00.000000"])";
StrptimeOptions options("%m/%d/%Y %z", TimeUnit::MICRO);
StrptimeOptions options("%m/%d/%Y %z", TimeUnit::MICRO, /*error_is_null=*/true);
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO, "UTC"), output1,
&options);
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions cpp/src/arrow/compute/api_scalar.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -353,7 +353,8 @@ static auto kStrftimeOptionsType = GetFunctionOptionsType<StrftimeOptions>(
DataMember("format", &StrftimeOptions::format));
static auto kStrptimeOptionsType = GetFunctionOptionsType<StrptimeOptions>(
DataMember("format", &StrptimeOptions::format),
DataMember("unit", &StrptimeOptions::unit));
DataMember("unit", &StrptimeOptions::unit),
DataMember("error_is_null", &StrptimeOptions::error_is_null));
static auto kStructFieldOptionsType = GetFunctionOptionsType<StructFieldOptions>(
DataMember("indices", &StructFieldOptions::indices));
static auto kTrimOptionsType = GetFunctionOptionsType<TrimOptions>(
Expand DownExpand Up@@ -544,11 +545,13 @@ StrftimeOptions::StrftimeOptions() : StrftimeOptions(kDefaultFormat) {}
constexpr char StrftimeOptions::kTypeName[];
constexpr const char* StrftimeOptions::kDefaultFormat;

StrptimeOptions::StrptimeOptions(std::string format, TimeUnit::type unit)
StrptimeOptions::StrptimeOptions(std::string format, TimeUnit::type unit,
bool error_is_null)
: FunctionOptions(internal::kStrptimeOptionsType),
format(std::move(format)),
unit(unit) {}
StrptimeOptions::StrptimeOptions() : StrptimeOptions("", TimeUnit::SECOND) {}
unit(unit),
error_is_null(error_is_null) {}
StrptimeOptions::StrptimeOptions() : StrptimeOptions("", TimeUnit::MICRO, false) {}
constexpr char StrptimeOptions::kTypeName[];

StructFieldOptions::StructFieldOptions(std::vector<int> indices)
Expand DownExpand Up@@ -822,6 +825,10 @@ Result<Datum> Strftime(const Datum& arg, StrftimeOptions options, ExecContext* c
return CallFunction("strftime", {arg}, &options, ctx);
}

Result<Datum> Strptime(const Datum& arg, StrptimeOptions options, ExecContext* ctx) {
return CallFunction("strptime", {arg}, &options, ctx);
}

Result<Datum> Week(const Datum& arg, WeekOptions options, ExecContext* ctx) {
return CallFunction("week", {arg}, &options, ctx);
}
Expand Down
23 changes: 22 additions & 1 deletion cpp/src/arrow/compute/api_scalar.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -267,12 +267,17 @@ class ARROW_EXPORT StructFieldOptions : public FunctionOptions {

class ARROW_EXPORT StrptimeOptions : public FunctionOptions {
public:
explicit StrptimeOptions(std::string format, TimeUnit::type unit);
explicit StrptimeOptions(std::string format, TimeUnit::type unit,
bool error_is_null = false);
StrptimeOptions();
static constexpr char const kTypeName[] = "StrptimeOptions";

/// The desired format string.
std::string format;
/// The desired time resolution
TimeUnit::type unit;
/// Return null on parsing errors if true or raise if false
bool error_is_null;
};

class ARROW_EXPORT StrftimeOptions : public FunctionOptions {
Expand DownExpand Up@@ -1398,6 +1403,22 @@ ARROW_EXPORT Result<Datum> Subsecond(const Datum& values, ExecContext* ctx = NUL
ARROW_EXPORT Result<Datum> Strftime(const Datum& values, StrftimeOptions options,
ExecContext* ctx = NULLPTR);

/// \brief Parse timestamps according to a format string
///
/// Return parsed timestamps according to the format string
/// `StrptimeOptions::format` at time resolution `Strftime::unit`. Parse errors are
/// raised depending on the `Strftime::error_is_null` setting.
///
/// \param[in] values input strings
/// \param[in] options for setting format string, unit and error_is_null
/// \param[in] ctx the function execution context, optional
/// \return the resulting datum
///
/// \since 8.0.0
/// \note API not yet finalized
ARROW_EXPORT Result<Datum> Strptime(const Datum& values, StrptimeOptions options,
ExecContext* ctx = NULLPTR);

/// \brief Converts timestamps from local timestamp without a timezone to a timestamp with
/// timezone, interpreting the local timestamp as being in the specified timezone for each
/// element of `values`
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/arrow/compute/exec/expression_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -737,7 +737,7 @@ TEST(Expression, ExecuteCall) {
])"));

ExpectExecute(call("strptime", {field_ref("a")},
compute::StrptimeOptions("%m/%d/%Y", TimeUnit::MICRO)),
compute::StrptimeOptions("%m/%d/%Y", TimeUnit::MICRO, true)),
ArrayFromJSON(struct_({field("a", utf8())}), R"([
{"a": "5/1/2020"},
{"a": null},
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/arrow/compute/function_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,7 +88,7 @@ TEST(FunctionOptions, Equality) {
options.emplace_back(new ExtractRegexOptions("pattern2"));
options.emplace_back(new SetLookupOptions(ArrayFromJSON(int64(), "[1, 2, 3, 4]")));
options.emplace_back(new SetLookupOptions(ArrayFromJSON(boolean(), "[true, false]")));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::MILLI));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::MILLI, true));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::NANO));
options.emplace_back(new StrftimeOptions("%Y-%m-%dT%H:%M:%SZ", "C"));
#ifndef _WIN32
Expand Down
74 changes: 0 additions & 74 deletions cpp/src/arrow/compute/kernels/scalar_string_ascii.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -2770,79 +2770,6 @@ void AddAsciiStringSplitRegex(FunctionRegistry* registry) {
}
#endif // ARROW_WITH_RE2

// ----------------------------------------------------------------------
// strptime string parsing

using StrptimeState = OptionsWrapper<StrptimeOptions>;

struct ParseStrptime {
explicit ParseStrptime(const StrptimeOptions& options)
: parser(TimestampParser::MakeStrptime(options.format)), unit(options.unit) {}

template <typename... Ignored>
int64_t Call(KernelContext*, util::string_view val, Status* st) const {
int64_t result = 0;
if (!(*parser)(val.data(), val.size(), unit, &result)) {
*st = Status::Invalid("Failed to parse string: '", val, "' as a scalar of type ",
TimestampType(unit).ToString());
}
return result;
}

std::shared_ptr<TimestampParser> parser;
TimeUnit::type unit;
};

template <typename InputType>
struct StrptimeExec {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
applicator::ScalarUnaryNotNullStateful<TimestampType, InputType, ParseStrptime>
kernel{ParseStrptime(StrptimeState::Get(ctx))};
return kernel.Exec(ctx, batch, out);
}
};

Result<ValueDescr> ResolveStrptimeOutput(KernelContext* ctx,
const std::vector<ValueDescr>&) {
if (!ctx->state()) {
return Status::Invalid("strptime does not provide default StrptimeOptions");
}
const StrptimeOptions& options = StrptimeState::Get(ctx);
// Check for use of %z or %Z
size_t cur = 0;
std::string zone = "";
while (cur < options.format.size() - 1) {
if (options.format[cur] == '%') {
if (options.format[cur + 1] == 'z') {
zone = "UTC";
break;
}
cur++;
}
cur++;
}
return ::arrow::timestamp(options.unit, zone);
}

const FunctionDoc strptime_doc(
"Parse timestamps",
("For each string in `strings`, parse it as a timestamp.\n"
"The timestamp unit and the expected string pattern must be given\n"
"in StrptimeOptions. Null inputs emit null. If a non-null string\n"
"fails parsing, an error is returned."),
{"strings"}, "StrptimeOptions", /*options_required=*/true);

void AddAsciiStringStrptime(FunctionRegistry* registry) {
auto func = std::make_shared<ScalarFunction>("strptime", Arity::Unary(), &strptime_doc);

OutputType out_ty(ResolveStrptimeOutput);
for (const auto& ty : StringTypes()) {
auto exec = GenerateVarBinaryToVarBinary<StrptimeExec>(ty);
DCHECK_OK(func->AddKernel({ty}, out_ty, std::move(exec), StrptimeState::Init));
}
DCHECK_OK(registry->AddFunction(std::move(func)));
}

// ----------------------------------------------------------------------
// Binary join

Expand DownExpand Up@@ -3518,7 +3445,6 @@ void RegisterScalarStringAscii(FunctionRegistry* registry) {
#ifdef ARROW_WITH_RE2
AddAsciiStringSplitRegex(registry);
#endif
AddAsciiStringStrptime(registry);
AddAsciiStringJoin(registry);
AddAsciiStringRepeat(registry);
}
Expand Down
34 changes: 27 additions & 7 deletions cpp/src/arrow/compute/kernels/scalar_string_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -1840,14 +1840,34 @@ TYPED_TEST(TestBaseBinaryKernels, ExtractRegexInvalid) {
#endif

TYPED_TEST(TestStringKernels, Strptime) {
std::string input1 = R"(["5/1/2020", null, "12/11/1900"])";
std::string output1 = R"(["2020-05-01", null, "1900-12-11"])";
StrptimeOptions options("%m/%d/%Y", TimeUnit::MICRO);
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO), output1, &options);
std::string input1 = R"(["5/1/2020", null, null, "12/13/1900", null])";
std::string input2 = R"(["5/1/2020", "12/13/1900"])";
std::string input3 = R"(["5/1/2020", "AA/BB/CCCC"])";
std::string input4 = R"(["5/1/2020", "AA/BB/CCCC", "AA/BB/CCCC", "AA/BB/CCCC", null])";
std::string input5 = R"(["5/1/2020 %z", null, null, "12/13/1900 %z", null])";
std::string output1 = R"(["2020-05-01", null, null, "1900-12-13", null])";
std::string output4 = R"(["2020-01-05", null, null, null, null])";
std::string output2 = R"(["2020-05-01", "1900-12-13"])";
std::string output3 = R"(["2020-05-01", null])";

StrptimeOptions options("%m/%d/%Y", TimeUnit::MICRO, /*error_is_null=*/true);
auto unit = timestamp(TimeUnit::MICRO);
this->CheckUnary("strptime", input1, unit, output1, &options);
this->CheckUnary("strptime", input2, unit, output2, &options);
this->CheckUnary("strptime", input3, unit, output3, &options);

options.format = "%d/%m/%Y";
this->CheckUnary("strptime", input4, unit, output4, &options);

input1 = R"(["5/1/2020 %z", null, "12/11/1900 %z"])";
options.format = "%m/%d/%Y %%z";
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO), output1, &options);
this->CheckUnary("strptime", input5, unit, output1, &options);

options.error_is_null = false;
this->CheckUnary("strptime", input5, unit, output1, &options);

EXPECT_RAISES_WITH_MESSAGE_THAT(
Invalid, testing::HasSubstr("Invalid: Failed to parse string: '5/1/2020'"),
Strptime(ArrayFromJSON(this->type(), input1), options));
}

TYPED_TEST(TestStringKernels, StrptimeZoneOffset) {
Expand All@@ -1859,7 +1879,7 @@ TYPED_TEST(TestStringKernels, StrptimeZoneOffset) {
std::string input1 = R"(["5/1/2020 +0100", null, "12/11/1900 -0130"])";
std::string output1 =
R"(["2020-04-30T23:00:00.000000", null, "1900-12-11T01:30:00.000000"])";
StrptimeOptions options("%m/%d/%Y %z", TimeUnit::MICRO);
StrptimeOptions options("%m/%d/%Y %z", TimeUnit::MICRO, /*error_is_null=*/true);
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO, "UTC"), output1,
&options);
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions cpp/src/arrow/compute/api_scalar.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -353,7 +353,8 @@ static auto kStrftimeOptionsType = GetFunctionOptionsType<StrftimeOptions>(
DataMember("format", &StrftimeOptions::format));
static auto kStrptimeOptionsType = GetFunctionOptionsType<StrptimeOptions>(
DataMember("format", &StrptimeOptions::format),
DataMember("unit", &StrptimeOptions::unit));
DataMember("unit", &StrptimeOptions::unit),
DataMember("error_is_null", &StrptimeOptions::error_is_null));
static auto kStructFieldOptionsType = GetFunctionOptionsType<StructFieldOptions>(
DataMember("indices", &StructFieldOptions::indices));
static auto kTrimOptionsType = GetFunctionOptionsType<TrimOptions>(
Expand DownExpand Up@@ -544,11 +545,13 @@ StrftimeOptions::StrftimeOptions() : StrftimeOptions(kDefaultFormat) {}
constexpr char StrftimeOptions::kTypeName[];
constexpr const char* StrftimeOptions::kDefaultFormat;

StrptimeOptions::StrptimeOptions(std::string format, TimeUnit::type unit)
StrptimeOptions::StrptimeOptions(std::string format, TimeUnit::type unit,
bool error_is_null)
: FunctionOptions(internal::kStrptimeOptionsType),
format(std::move(format)),
unit(unit) {}
StrptimeOptions::StrptimeOptions() : StrptimeOptions("", TimeUnit::SECOND) {}
unit(unit),
error_is_null(error_is_null) {}
StrptimeOptions::StrptimeOptions() : StrptimeOptions("", TimeUnit::MICRO, false) {}
constexpr char StrptimeOptions::kTypeName[];

StructFieldOptions::StructFieldOptions(std::vector<int> indices)
Expand DownExpand Up@@ -822,6 +825,10 @@ Result<Datum> Strftime(const Datum& arg, StrftimeOptions options, ExecContext* c
return CallFunction("strftime", {arg}, &options, ctx);
}

Result<Datum> Strptime(const Datum& arg, StrptimeOptions options, ExecContext* ctx) {
return CallFunction("strptime", {arg}, &options, ctx);
}

Result<Datum> Week(const Datum& arg, WeekOptions options, ExecContext* ctx) {
return CallFunction("week", {arg}, &options, ctx);
}
Expand Down
23 changes: 22 additions & 1 deletion cpp/src/arrow/compute/api_scalar.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -267,12 +267,17 @@ class ARROW_EXPORT StructFieldOptions : public FunctionOptions {

class ARROW_EXPORT StrptimeOptions : public FunctionOptions {
public:
explicit StrptimeOptions(std::string format, TimeUnit::type unit);
explicit StrptimeOptions(std::string format, TimeUnit::type unit,
bool error_is_null = false);
StrptimeOptions();
static constexpr char const kTypeName[] = "StrptimeOptions";

/// The desired format string.
std::string format;
/// The desired time resolution
TimeUnit::type unit;
/// Return null on parsing errors if true or raise if false
bool error_is_null;
};

class ARROW_EXPORT StrftimeOptions : public FunctionOptions {
Expand DownExpand Up@@ -1398,6 +1403,22 @@ ARROW_EXPORT Result<Datum> Subsecond(const Datum& values, ExecContext* ctx = NUL
ARROW_EXPORT Result<Datum> Strftime(const Datum& values, StrftimeOptions options,
ExecContext* ctx = NULLPTR);

/// \brief Parse timestamps according to a format string
///
/// Return parsed timestamps according to the format string
/// `StrptimeOptions::format` at time resolution `Strftime::unit`. Parse errors are
/// raised depending on the `Strftime::error_is_null` setting.
///
/// \param[in] values input strings
/// \param[in] options for setting format string, unit and error_is_null
/// \param[in] ctx the function execution context, optional
/// \return the resulting datum
///
/// \since 8.0.0
/// \note API not yet finalized
ARROW_EXPORT Result<Datum> Strptime(const Datum& values, StrptimeOptions options,
ExecContext* ctx = NULLPTR);

/// \brief Converts timestamps from local timestamp without a timezone to a timestamp with
/// timezone, interpreting the local timestamp as being in the specified timezone for each
/// element of `values`
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/arrow/compute/exec/expression_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -737,7 +737,7 @@ TEST(Expression, ExecuteCall) {
])"));

ExpectExecute(call("strptime", {field_ref("a")},
compute::StrptimeOptions("%m/%d/%Y", TimeUnit::MICRO)),
compute::StrptimeOptions("%m/%d/%Y", TimeUnit::MICRO, true)),
ArrayFromJSON(struct_({field("a", utf8())}), R"([
{"a": "5/1/2020"},
{"a": null},
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/arrow/compute/function_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,7 +88,7 @@ TEST(FunctionOptions, Equality) {
options.emplace_back(new ExtractRegexOptions("pattern2"));
options.emplace_back(new SetLookupOptions(ArrayFromJSON(int64(), "[1, 2, 3, 4]")));
options.emplace_back(new SetLookupOptions(ArrayFromJSON(boolean(), "[true, false]")));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::MILLI));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::MILLI, true));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::NANO));
options.emplace_back(new StrftimeOptions("%Y-%m-%dT%H:%M:%SZ", "C"));
#ifndef _WIN32
Expand Down
74 changes: 0 additions & 74 deletions cpp/src/arrow/compute/kernels/scalar_string_ascii.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -2770,79 +2770,6 @@ void AddAsciiStringSplitRegex(FunctionRegistry* registry) {
}
#endif // ARROW_WITH_RE2

// ----------------------------------------------------------------------
// strptime string parsing

using StrptimeState = OptionsWrapper<StrptimeOptions>;

struct ParseStrptime {
explicit ParseStrptime(const StrptimeOptions& options)
: parser(TimestampParser::MakeStrptime(options.format)), unit(options.unit) {}

template <typename... Ignored>
int64_t Call(KernelContext*, util::string_view val, Status* st) const {
int64_t result = 0;
if (!(*parser)(val.data(), val.size(), unit, &result)) {
*st = Status::Invalid("Failed to parse string: '", val, "' as a scalar of type ",
TimestampType(unit).ToString());
}
return result;
}

std::shared_ptr<TimestampParser> parser;
TimeUnit::type unit;
};

template <typename InputType>
struct StrptimeExec {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
applicator::ScalarUnaryNotNullStateful<TimestampType, InputType, ParseStrptime>
kernel{ParseStrptime(StrptimeState::Get(ctx))};
return kernel.Exec(ctx, batch, out);
}
};

Result<ValueDescr> ResolveStrptimeOutput(KernelContext* ctx,
const std::vector<ValueDescr>&) {
if (!ctx->state()) {
return Status::Invalid("strptime does not provide default StrptimeOptions");
}
const StrptimeOptions& options = StrptimeState::Get(ctx);
// Check for use of %z or %Z
size_t cur = 0;
std::string zone = "";
while (cur < options.format.size() - 1) {
if (options.format[cur] == '%') {
if (options.format[cur + 1] == 'z') {
zone = "UTC";
break;
}
cur++;
}
cur++;
}
return ::arrow::timestamp(options.unit, zone);
}

const FunctionDoc strptime_doc(
"Parse timestamps",
("For each string in `strings`, parse it as a timestamp.\n"
"The timestamp unit and the expected string pattern must be given\n"
"in StrptimeOptions. Null inputs emit null. If a non-null string\n"
"fails parsing, an error is returned."),
{"strings"}, "StrptimeOptions", /*options_required=*/true);

void AddAsciiStringStrptime(FunctionRegistry* registry) {
auto func = std::make_shared<ScalarFunction>("strptime", Arity::Unary(), &strptime_doc);

OutputType out_ty(ResolveStrptimeOutput);
for (const auto& ty : StringTypes()) {
auto exec = GenerateVarBinaryToVarBinary<StrptimeExec>(ty);
DCHECK_OK(func->AddKernel({ty}, out_ty, std::move(exec), StrptimeState::Init));
}
DCHECK_OK(registry->AddFunction(std::move(func)));
}

// ----------------------------------------------------------------------
// Binary join

Expand DownExpand Up@@ -3518,7 +3445,6 @@ void RegisterScalarStringAscii(FunctionRegistry* registry) {
#ifdef ARROW_WITH_RE2
AddAsciiStringSplitRegex(registry);
#endif
AddAsciiStringStrptime(registry);
AddAsciiStringJoin(registry);
AddAsciiStringRepeat(registry);
}
Expand Down
34 changes: 27 additions & 7 deletions cpp/src/arrow/compute/kernels/scalar_string_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -1840,14 +1840,34 @@ TYPED_TEST(TestBaseBinaryKernels, ExtractRegexInvalid) {
#endif

TYPED_TEST(TestStringKernels, Strptime) {
std::string input1 = R"(["5/1/2020", null, "12/11/1900"])";
std::string output1 = R"(["2020-05-01", null, "1900-12-11"])";
StrptimeOptions options("%m/%d/%Y", TimeUnit::MICRO);
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO), output1, &options);
std::string input1 = R"(["5/1/2020", null, null, "12/13/1900", null])";
std::string input2 = R"(["5/1/2020", "12/13/1900"])";
std::string input3 = R"(["5/1/2020", "AA/BB/CCCC"])";
std::string input4 = R"(["5/1/2020", "AA/BB/CCCC", "AA/BB/CCCC", "AA/BB/CCCC", null])";
std::string input5 = R"(["5/1/2020 %z", null, null, "12/13/1900 %z", null])";
std::string output1 = R"(["2020-05-01", null, null, "1900-12-13", null])";
std::string output4 = R"(["2020-01-05", null, null, null, null])";
std::string output2 = R"(["2020-05-01", "1900-12-13"])";
std::string output3 = R"(["2020-05-01", null])";

StrptimeOptions options("%m/%d/%Y", TimeUnit::MICRO, /*error_is_null=*/true);
auto unit = timestamp(TimeUnit::MICRO);
this->CheckUnary("strptime", input1, unit, output1, &options);
this->CheckUnary("strptime", input2, unit, output2, &options);
this->CheckUnary("strptime", input3, unit, output3, &options);

options.format = "%d/%m/%Y";
this->CheckUnary("strptime", input4, unit, output4, &options);

input1 = R"(["5/1/2020 %z", null, "12/11/1900 %z"])";
options.format = "%m/%d/%Y %%z";
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO), output1, &options);
this->CheckUnary("strptime", input5, unit, output1, &options);

options.error_is_null = false;
this->CheckUnary("strptime", input5, unit, output1, &options);

EXPECT_RAISES_WITH_MESSAGE_THAT(
Invalid, testing::HasSubstr("Invalid: Failed to parse string: '5/1/2020'"),
Strptime(ArrayFromJSON(this->type(), input1), options));
}

TYPED_TEST(TestStringKernels, StrptimeZoneOffset) {
Expand All@@ -1859,7 +1879,7 @@ TYPED_TEST(TestStringKernels, StrptimeZoneOffset) {
std::string input1 = R"(["5/1/2020 +0100", null, "12/11/1900 -0130"])";
std::string output1 =
R"(["2020-04-30T23:00:00.000000", null, "1900-12-11T01:30:00.000000"])";
StrptimeOptions options("%m/%d/%Y %z", TimeUnit::MICRO);
StrptimeOptions options("%m/%d/%Y %z", TimeUnit::MICRO, /*error_is_null=*/true);
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO, "UTC"), output1,
&options);
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions cpp/src/arrow/compute/api_scalar.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -353,7 +353,8 @@ static auto kStrftimeOptionsType = GetFunctionOptionsType<StrftimeOptions>(
DataMember("format", &StrftimeOptions::format));
static auto kStrptimeOptionsType = GetFunctionOptionsType<StrptimeOptions>(
DataMember("format", &StrptimeOptions::format),
DataMember("unit", &StrptimeOptions::unit));
DataMember("unit", &StrptimeOptions::unit),
DataMember("error_is_null", &StrptimeOptions::error_is_null));
static auto kStructFieldOptionsType = GetFunctionOptionsType<StructFieldOptions>(
DataMember("indices", &StructFieldOptions::indices));
static auto kTrimOptionsType = GetFunctionOptionsType<TrimOptions>(
Expand DownExpand Up@@ -544,11 +545,13 @@ StrftimeOptions::StrftimeOptions() : StrftimeOptions(kDefaultFormat) {}
constexpr char StrftimeOptions::kTypeName[];
constexpr const char* StrftimeOptions::kDefaultFormat;

StrptimeOptions::StrptimeOptions(std::string format, TimeUnit::type unit)
StrptimeOptions::StrptimeOptions(std::string format, TimeUnit::type unit,
bool error_is_null)
: FunctionOptions(internal::kStrptimeOptionsType),
format(std::move(format)),
unit(unit) {}
StrptimeOptions::StrptimeOptions() : StrptimeOptions("", TimeUnit::SECOND) {}
unit(unit),
error_is_null(error_is_null) {}
StrptimeOptions::StrptimeOptions() : StrptimeOptions("", TimeUnit::MICRO, false) {}
constexpr char StrptimeOptions::kTypeName[];

StructFieldOptions::StructFieldOptions(std::vector<int> indices)
Expand DownExpand Up@@ -822,6 +825,10 @@ Result<Datum> Strftime(const Datum& arg, StrftimeOptions options, ExecContext* c
return CallFunction("strftime", {arg}, &options, ctx);
}

Result<Datum> Strptime(const Datum& arg, StrptimeOptions options, ExecContext* ctx) {
return CallFunction("strptime", {arg}, &options, ctx);
}

Result<Datum> Week(const Datum& arg, WeekOptions options, ExecContext* ctx) {
return CallFunction("week", {arg}, &options, ctx);
}
Expand Down
23 changes: 22 additions & 1 deletion cpp/src/arrow/compute/api_scalar.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -267,12 +267,17 @@ class ARROW_EXPORT StructFieldOptions : public FunctionOptions {

class ARROW_EXPORT StrptimeOptions : public FunctionOptions {
public:
explicit StrptimeOptions(std::string format, TimeUnit::type unit);
explicit StrptimeOptions(std::string format, TimeUnit::type unit,
bool error_is_null = false);
StrptimeOptions();
static constexpr char const kTypeName[] = "StrptimeOptions";

/// The desired format string.
std::string format;
/// The desired time resolution
TimeUnit::type unit;
/// Return null on parsing errors if true or raise if false
bool error_is_null;
};

class ARROW_EXPORT StrftimeOptions : public FunctionOptions {
Expand DownExpand Up@@ -1398,6 +1403,22 @@ ARROW_EXPORT Result<Datum> Subsecond(const Datum& values, ExecContext* ctx = NUL
ARROW_EXPORT Result<Datum> Strftime(const Datum& values, StrftimeOptions options,
ExecContext* ctx = NULLPTR);

/// \brief Parse timestamps according to a format string
///
/// Return parsed timestamps according to the format string
/// `StrptimeOptions::format` at time resolution `Strftime::unit`. Parse errors are
/// raised depending on the `Strftime::error_is_null` setting.
///
/// \param[in] values input strings
/// \param[in] options for setting format string, unit and error_is_null
/// \param[in] ctx the function execution context, optional
/// \return the resulting datum
///
/// \since 8.0.0
/// \note API not yet finalized
ARROW_EXPORT Result<Datum> Strptime(const Datum& values, StrptimeOptions options,
ExecContext* ctx = NULLPTR);

/// \brief Converts timestamps from local timestamp without a timezone to a timestamp with
/// timezone, interpreting the local timestamp as being in the specified timezone for each
/// element of `values`
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/arrow/compute/exec/expression_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -737,7 +737,7 @@ TEST(Expression, ExecuteCall) {
])"));

ExpectExecute(call("strptime", {field_ref("a")},
compute::StrptimeOptions("%m/%d/%Y", TimeUnit::MICRO)),
compute::StrptimeOptions("%m/%d/%Y", TimeUnit::MICRO, true)),
ArrayFromJSON(struct_({field("a", utf8())}), R"([
{"a": "5/1/2020"},
{"a": null},
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/arrow/compute/function_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,7 +88,7 @@ TEST(FunctionOptions, Equality) {
options.emplace_back(new ExtractRegexOptions("pattern2"));
options.emplace_back(new SetLookupOptions(ArrayFromJSON(int64(), "[1, 2, 3, 4]")));
options.emplace_back(new SetLookupOptions(ArrayFromJSON(boolean(), "[true, false]")));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::MILLI));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::MILLI, true));
options.emplace_back(new StrptimeOptions("%Y", TimeUnit::type::NANO));
options.emplace_back(new StrftimeOptions("%Y-%m-%dT%H:%M:%SZ", "C"));
#ifndef _WIN32
Expand Down
74 changes: 0 additions & 74 deletions cpp/src/arrow/compute/kernels/scalar_string_ascii.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -2770,79 +2770,6 @@ void AddAsciiStringSplitRegex(FunctionRegistry* registry) {
}
#endif // ARROW_WITH_RE2

// ----------------------------------------------------------------------
// strptime string parsing

using StrptimeState = OptionsWrapper<StrptimeOptions>;

struct ParseStrptime {
explicit ParseStrptime(const StrptimeOptions& options)
: parser(TimestampParser::MakeStrptime(options.format)), unit(options.unit) {}

template <typename... Ignored>
int64_t Call(KernelContext*, util::string_view val, Status* st) const {
int64_t result = 0;
if (!(*parser)(val.data(), val.size(), unit, &result)) {
*st = Status::Invalid("Failed to parse string: '", val, "' as a scalar of type ",
TimestampType(unit).ToString());
}
return result;
}

std::shared_ptr<TimestampParser> parser;
TimeUnit::type unit;
};

template <typename InputType>
struct StrptimeExec {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
applicator::ScalarUnaryNotNullStateful<TimestampType, InputType, ParseStrptime>
kernel{ParseStrptime(StrptimeState::Get(ctx))};
return kernel.Exec(ctx, batch, out);
}
};

Result<ValueDescr> ResolveStrptimeOutput(KernelContext* ctx,
const std::vector<ValueDescr>&) {
if (!ctx->state()) {
return Status::Invalid("strptime does not provide default StrptimeOptions");
}
const StrptimeOptions& options = StrptimeState::Get(ctx);
// Check for use of %z or %Z
size_t cur = 0;
std::string zone = "";
while (cur < options.format.size() - 1) {
if (options.format[cur] == '%') {
if (options.format[cur + 1] == 'z') {
zone = "UTC";
break;
}
cur++;
}
cur++;
}
return ::arrow::timestamp(options.unit, zone);
}

const FunctionDoc strptime_doc(
"Parse timestamps",
("For each string in `strings`, parse it as a timestamp.\n"
"The timestamp unit and the expected string pattern must be given\n"
"in StrptimeOptions. Null inputs emit null. If a non-null string\n"
"fails parsing, an error is returned."),
{"strings"}, "StrptimeOptions", /*options_required=*/true);

void AddAsciiStringStrptime(FunctionRegistry* registry) {
auto func = std::make_shared<ScalarFunction>("strptime", Arity::Unary(), &strptime_doc);

OutputType out_ty(ResolveStrptimeOutput);
for (const auto& ty : StringTypes()) {
auto exec = GenerateVarBinaryToVarBinary<StrptimeExec>(ty);
DCHECK_OK(func->AddKernel({ty}, out_ty, std::move(exec), StrptimeState::Init));
}
DCHECK_OK(registry->AddFunction(std::move(func)));
}

// ----------------------------------------------------------------------
// Binary join

Expand DownExpand Up@@ -3518,7 +3445,6 @@ void RegisterScalarStringAscii(FunctionRegistry* registry) {
#ifdef ARROW_WITH_RE2
AddAsciiStringSplitRegex(registry);
#endif
AddAsciiStringStrptime(registry);
AddAsciiStringJoin(registry);
AddAsciiStringRepeat(registry);
}
Expand Down
34 changes: 27 additions & 7 deletions cpp/src/arrow/compute/kernels/scalar_string_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -1840,14 +1840,34 @@ TYPED_TEST(TestBaseBinaryKernels, ExtractRegexInvalid) {
#endif

TYPED_TEST(TestStringKernels, Strptime) {
std::string input1 = R"(["5/1/2020", null, "12/11/1900"])";
std::string output1 = R"(["2020-05-01", null, "1900-12-11"])";
StrptimeOptions options("%m/%d/%Y", TimeUnit::MICRO);
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO), output1, &options);
std::string input1 = R"(["5/1/2020", null, null, "12/13/1900", null])";
std::string input2 = R"(["5/1/2020", "12/13/1900"])";
std::string input3 = R"(["5/1/2020", "AA/BB/CCCC"])";
std::string input4 = R"(["5/1/2020", "AA/BB/CCCC", "AA/BB/CCCC", "AA/BB/CCCC", null])";
std::string input5 = R"(["5/1/2020 %z", null, null, "12/13/1900 %z", null])";
std::string output1 = R"(["2020-05-01", null, null, "1900-12-13", null])";
std::string output4 = R"(["2020-01-05", null, null, null, null])";
std::string output2 = R"(["2020-05-01", "1900-12-13"])";
std::string output3 = R"(["2020-05-01", null])";

StrptimeOptions options("%m/%d/%Y", TimeUnit::MICRO, /*error_is_null=*/true);
auto unit = timestamp(TimeUnit::MICRO);
this->CheckUnary("strptime", input1, unit, output1, &options);
this->CheckUnary("strptime", input2, unit, output2, &options);
this->CheckUnary("strptime", input3, unit, output3, &options);

options.format = "%d/%m/%Y";
this->CheckUnary("strptime", input4, unit, output4, &options);

input1 = R"(["5/1/2020 %z", null, "12/11/1900 %z"])";
options.format = "%m/%d/%Y %%z";
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO), output1, &options);
this->CheckUnary("strptime", input5, unit, output1, &options);

options.error_is_null = false;
this->CheckUnary("strptime", input5, unit, output1, &options);

EXPECT_RAISES_WITH_MESSAGE_THAT(
Invalid, testing::HasSubstr("Invalid: Failed to parse string: '5/1/2020'"),
Strptime(ArrayFromJSON(this->type(), input1), options));
}

TYPED_TEST(TestStringKernels, StrptimeZoneOffset) {
Expand All@@ -1859,7 +1879,7 @@ TYPED_TEST(TestStringKernels, StrptimeZoneOffset) {
std::string input1 = R"(["5/1/2020 +0100", null, "12/11/1900 -0130"])";
std::string output1 =
R"(["2020-04-30T23:00:00.000000", null, "1900-12-11T01:30:00.000000"])";
StrptimeOptions options("%m/%d/%Y %z", TimeUnit::MICRO);
StrptimeOptions options("%m/%d/%Y %z", TimeUnit::MICRO, /*error_is_null=*/true);
this->CheckUnary("strptime", input1, timestamp(TimeUnit::MICRO, "UTC"), output1,
&options);
}
Expand Down
Loading