Skip to content

Commit

Permalink
feat: add strftime to java date format converter
Browse files Browse the repository at this point in the history
Signed-off-by: JP-Ellis <[email protected]>
  • Loading branch information
JP-Ellis committed Sep 30, 2024
1 parent 9662008 commit 57aeb3b
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 0 deletions.
142 changes: 142 additions & 0 deletions src/pact/v3/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
"""
Utility functions for Pact.
This module defines a number of utility functions that are used in specific
contexts within the Pact library. These functions are not intended to be
used directly by consumers of the library, but are still made available for
reference.
"""

import warnings

_PYTHON_FORMAT_TO_JAVA_DATETIME = {
"a": "EEE",
"A": "EEEE",
"b": "MMM",
"B": "MMMM",
# c is locale dependent, so we can't convert it directly.
"d": "dd",
"f": "SSSSSS",
"G": "YYYY",
"H": "HH",
"I": "hh",
"j": "DDD",
"m": "MM",
"M": "mm",
"p": "a",
"S": "ss",
"u": "u",
"U": "ww",
"V": "ww",
# w is 0-indexed in Python, but 1-indexed in Java.
"W": "ww",
# x is locale dependent, so we can't convert it directly.
# X is locale dependent, so we can't convert it directly.
"y": "yy",
"Y": "yyyy",
"z": "Z",
"Z": "z",
"%": "%",
":z": "XXX",
}


def strftime_to_simple_date_format(python_format: str) -> str:
"""
Convert a Python datetime format string to Java SimpleDateFormat format.
Python uses [`strftime`
codes](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes)
which are ultimately based on the C `strftime` function. Java uses
[`SimpleDateFormat`
codes](https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html)
which generally have corresponding codes, but with some differences.
Note that this function strictly supports codes explicitly defined in the
Python documentation. Locale-dependent codes are not supported, and codes
supported by the underlying C library but not Python are not supported. For
examples, `%c`, `%x`, and `%X` are not supported as they are locale
dependent, and `%D` is not supported as it is not part of the Python
documentation (even though it may be supported by the underlying C and
therefore work in some Python implementations).
Args:
python_format:
The Python datetime format string to convert.
Returns:
The equivalent Java SimpleDateFormat format string.
"""
# Each Python format code is exactly two characters long, so we can
# safely iterate through the string.
idx = 0
result: str = ""
escaped = False

while idx < len(python_format):
c = python_format[idx]
idx += 1

if c == "%":
c = python_format[idx]
if escaped:
result += "'"
escaped = False
result += _format_code_to_java_format(c)
# Increment another time to skip the second character of the
# Python format code.
idx += 1
continue

if c == "'":
# In Java, single quotes are used to escape characters.
# To insert a single quote, we need to insert two single quotes.
# It doesn't matter if we're in an escape sequence or not, as
# Java treats them the same.
result += "''"
continue

if not escaped and c.isalpha():
result += "'"
escaped = True
result += c

if escaped:
result += "'"
return result


def _format_code_to_java_format(code: str) -> str:
"""
Convert a single Python format code to a Java SimpleDateFormat format.
Args:
code:
The Python format code to convert, without the leading `%`. This
will typically be a single character, but may be two characters
for some codes.
Returns:
The equivalent Java SimpleDateFormat format string.
"""
if code in ["U", "V", "W"]:
warnings.warn(
f"The Java equivalent for `%{code}` is locale dependent.",
stacklevel=3,
)

# The following are locale-dependent, and aren't directly convertible.
if code in ["c", "x", "X"]:
msg = f"Cannot convert locale-dependent Python format code `%{code}` to Java"
raise ValueError(msg)

# The following codes simply do not have a direct equivalent in Java.
if code in ["w"]:
msg = f"Python format code `%{code}` is not supported in Java"
raise ValueError(msg)

if code in _PYTHON_FORMAT_TO_JAVA_DATETIME:
return _PYTHON_FORMAT_TO_JAVA_DATETIME[code]

msg = f"Unsupported Python format code `%{code}`"
raise ValueError(msg)
36 changes: 36 additions & 0 deletions tests/v3/test_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
Tests of pact.v3.util functions.
"""

import pytest

from pact.v3.util import strftime_to_simple_date_format


def test_convert_python_to_java_datetime_format_basic() -> None:
assert strftime_to_simple_date_format("%Y-%m-%d") == "yyyy-MM-dd"
assert strftime_to_simple_date_format("%H:%M:%S") == "HH:mm:ss"


def test_convert_python_to_java_datetime_format_with_unsupported_code() -> None:
with pytest.raises(
ValueError,
match="Cannot convert locale-dependent Python format code `%c` to Java",
):
strftime_to_simple_date_format("%c")


def test_convert_python_to_java_datetime_format_with_warning() -> None:
with pytest.warns(
UserWarning, match="The Java equivalent for `%U` is locale dependent."
):
assert strftime_to_simple_date_format("%U") == "ww"


def test_convert_python_to_java_datetime_format_with_escape_characters() -> None:
assert strftime_to_simple_date_format("'%Y-%m-%d'") == "''yyyy-MM-dd''"
assert strftime_to_simple_date_format("%%Y") == "%'Y'"


def test_convert_python_to_java_datetime_format_with_single_quote() -> None:
assert strftime_to_simple_date_format("%Y'%m'%d") == "yyyy''MM''dd"

0 comments on commit 57aeb3b

Please sign in to comment.