Skip to content

Commit 9b95af8

Browse files
authored
Use iodata for Calendar type formatting (#14130)
1 parent dc69977 commit 9b95af8

File tree

1 file changed

+230
-33
lines changed

1 file changed

+230
-33
lines changed

lib/elixir/lib/calendar/iso.ex

Lines changed: 230 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,37 @@ defmodule Calendar.ISO do
12201220
:basic | :extended
12211221
) :: String.t()
12221222
def time_to_string(
1223+
hour,
1224+
minute,
1225+
second,
1226+
microsecond,
1227+
format \\ :extended
1228+
) do
1229+
time_to_iodata(hour, minute, second, microsecond, format)
1230+
|> IO.iodata_to_binary()
1231+
end
1232+
1233+
@doc """
1234+
Converts the given time into a iodata.
1235+
1236+
See `time_to_string/5` for more information.
1237+
1238+
## Examples
1239+
1240+
iex> data = Calendar.ISO.time_to_iodata(2, 2, 2, {2, 6})
1241+
iex> IO.iodata_to_binary(data)
1242+
"02:02:02.000002"
1243+
1244+
"""
1245+
@doc since: "1.19.0"
1246+
@spec time_to_iodata(
1247+
Calendar.hour(),
1248+
Calendar.minute(),
1249+
Calendar.second(),
1250+
Calendar.microsecond(),
1251+
:basic | :extended
1252+
) :: iodata
1253+
def time_to_iodata(
12231254
hour,
12241255
minute,
12251256
second,
@@ -1228,24 +1259,42 @@ defmodule Calendar.ISO do
12281259
)
12291260
when is_hour(hour) and is_minute(minute) and is_second(second) and
12301261
is_microsecond(ms_value, ms_precision) and format in [:basic, :extended] do
1231-
time_to_string_guarded(hour, minute, second, microsecond, format)
1262+
time_to_iodata_guarded(hour, minute, second, microsecond, format)
1263+
end
1264+
1265+
defp time_to_iodata_guarded(hour, minute, second, {_, 0}, format) do
1266+
time_to_iodata_format(hour, minute, second, format)
12321267
end
12331268

1234-
defp time_to_string_guarded(hour, minute, second, {_, 0}, format) do
1235-
time_to_string_format(hour, minute, second, format)
1269+
defp time_to_iodata_guarded(hour, minute, second, {microsecond, precision}, format) do
1270+
[
1271+
time_to_iodata_format(hour, minute, second, format),
1272+
?.
1273+
| microseconds_to_iodata(microsecond, precision)
1274+
]
12361275
end
12371276

1238-
defp time_to_string_guarded(hour, minute, second, {microsecond, precision}, format) do
1239-
time_to_string_format(hour, minute, second, format) <>
1240-
"." <> (microsecond |> zero_pad(6) |> binary_part(0, precision))
1277+
@doc false
1278+
def microseconds_to_iodata(_microsecond, 0), do: []
1279+
def microseconds_to_iodata(microsecond, 6), do: zero_pad(microsecond, 6)
1280+
1281+
def microseconds_to_iodata(microsecond, precision) do
1282+
num = div(microsecond, div_factor(precision))
1283+
zero_pad(num, precision)
12411284
end
12421285

1243-
defp time_to_string_format(hour, minute, second, :extended) do
1244-
zero_pad(hour, 2) <> ":" <> zero_pad(minute, 2) <> ":" <> zero_pad(second, 2)
1286+
defp div_factor(1), do: 100_000
1287+
defp div_factor(2), do: 10_000
1288+
defp div_factor(3), do: 1_000
1289+
defp div_factor(4), do: 100
1290+
defp div_factor(5), do: 10
1291+
1292+
defp time_to_iodata_format(hour, minute, second, :extended) do
1293+
[zero_pad(hour, 2), ?:, zero_pad(minute, 2), ?: | zero_pad(second, 2)]
12451294
end
12461295

1247-
defp time_to_string_format(hour, minute, second, :basic) do
1248-
zero_pad(hour, 2) <> zero_pad(minute, 2) <> zero_pad(second, 2)
1296+
defp time_to_iodata_format(hour, minute, second, :basic) do
1297+
[zero_pad(hour, 2), zero_pad(minute, 2) | zero_pad(second, 2)]
12491298
end
12501299

12511300
@doc """
@@ -1273,18 +1322,36 @@ defmodule Calendar.ISO do
12731322
@doc since: "1.4.0"
12741323
@spec date_to_string(year, month, day, :basic | :extended) :: String.t()
12751324
@impl true
1276-
def date_to_string(year, month, day, format \\ :extended)
1325+
def date_to_string(year, month, day, format \\ :extended) do
1326+
date_to_iodata(year, month, day, format)
1327+
|> IO.iodata_to_binary()
1328+
end
1329+
1330+
@doc """
1331+
Converts the given date into a iodata.
1332+
1333+
See `date_to_string/4` for more information.
1334+
1335+
## Examples
1336+
1337+
iex> data = Calendar.ISO.date_to_iodata(2015, 2, 28)
1338+
iex> IO.iodata_to_binary(data)
1339+
"2015-02-28"
1340+
"""
1341+
@doc since: "1.19.0"
1342+
@spec date_to_iodata(year, month, day, :basic | :extended) :: iodata
1343+
def date_to_iodata(year, month, day, format \\ :extended)
12771344
when is_integer(year) and is_integer(month) and is_integer(day) and
12781345
format in [:basic, :extended] do
1279-
date_to_string_guarded(year, month, day, format)
1346+
date_to_iodata_guarded(year, month, day, format)
12801347
end
12811348

1282-
defp date_to_string_guarded(year, month, day, :extended) do
1283-
zero_pad(year, 4) <> "-" <> zero_pad(month, 2) <> "-" <> zero_pad(day, 2)
1349+
defp date_to_iodata_guarded(year, month, day, :extended) do
1350+
[zero_pad(year, 4), ?-, zero_pad(month, 2), ?- | zero_pad(day, 2)]
12841351
end
12851352

1286-
defp date_to_string_guarded(year, month, day, :basic) do
1287-
zero_pad(year, 4) <> zero_pad(month, 2) <> zero_pad(day, 2)
1353+
defp date_to_iodata_guarded(year, month, day, :basic) do
1354+
[zero_pad(year, 4), zero_pad(month, 2) | zero_pad(day, 2)]
12881355
end
12891356

12901357
@doc """
@@ -1327,8 +1394,61 @@ defmodule Calendar.ISO do
13271394
microsecond,
13281395
format \\ :extended
13291396
) do
1330-
date_to_string(year, month, day, format) <>
1331-
" " <> time_to_string(hour, minute, second, microsecond, format)
1397+
naive_datetime_to_iodata(
1398+
year,
1399+
month,
1400+
day,
1401+
hour,
1402+
minute,
1403+
second,
1404+
microsecond,
1405+
format
1406+
)
1407+
|> IO.iodata_to_binary()
1408+
end
1409+
1410+
@doc """
1411+
Converts the given naive_datetime into a iodata.
1412+
1413+
See `naive_datetime_to_iodata/8` for more information.
1414+
1415+
## Examples
1416+
1417+
iex> data = Calendar.ISO.naive_datetime_to_iodata(2015, 2, 28, 1, 2, 3, {4, 6}, :basic)
1418+
iex> IO.iodata_to_binary(data)
1419+
"20150228 010203.000004"
1420+
1421+
iex> data = Calendar.ISO.naive_datetime_to_iodata(2015, 2, 28, 1, 2, 3, {4, 6}, :extended)
1422+
iex> IO.iodata_to_binary(data)
1423+
"2015-02-28 01:02:03.000004"
1424+
1425+
"""
1426+
@doc since: "1.19.0"
1427+
@spec naive_datetime_to_string(
1428+
year,
1429+
month,
1430+
day,
1431+
Calendar.hour(),
1432+
Calendar.minute(),
1433+
Calendar.second(),
1434+
Calendar.microsecond(),
1435+
:basic | :extended
1436+
) :: iodata
1437+
def naive_datetime_to_iodata(
1438+
year,
1439+
month,
1440+
day,
1441+
hour,
1442+
minute,
1443+
second,
1444+
microsecond,
1445+
format \\ :extended
1446+
) do
1447+
[
1448+
date_to_iodata(year, month, day, format),
1449+
?\s
1450+
| time_to_iodata(hour, minute, second, microsecond, format)
1451+
]
13321452
end
13331453

13341454
@doc """
@@ -1394,20 +1514,89 @@ defmodule Calendar.ISO do
13941514
utc_offset,
13951515
std_offset,
13961516
format \\ :extended
1517+
) do
1518+
datetime_to_iodata(
1519+
year,
1520+
month,
1521+
day,
1522+
hour,
1523+
minute,
1524+
second,
1525+
microsecond,
1526+
time_zone,
1527+
zone_abbr,
1528+
utc_offset,
1529+
std_offset,
1530+
format
1531+
)
1532+
|> IO.iodata_to_binary()
1533+
end
1534+
1535+
@doc """
1536+
Converts the given datetime into a iodata.
1537+
1538+
See `datetime_to_iodata/12` for more information.
1539+
1540+
## Examples
1541+
1542+
iex> time_zone = "Etc/UTC"
1543+
iex> data = Calendar.ISO.datetime_to_iodata(2017, 8, 1, 1, 2, 3, {4, 5}, time_zone, "UTC", 0, 0)
1544+
iex> IO.iodata_to_binary(data)
1545+
"2017-08-01 01:02:03.00000Z"
1546+
1547+
"""
1548+
@doc since: "1.19.0"
1549+
@spec datetime_to_iodata(
1550+
year,
1551+
month,
1552+
day,
1553+
Calendar.hour(),
1554+
Calendar.minute(),
1555+
Calendar.second(),
1556+
Calendar.microsecond(),
1557+
Calendar.time_zone(),
1558+
Calendar.zone_abbr(),
1559+
Calendar.utc_offset(),
1560+
Calendar.std_offset(),
1561+
:basic | :extended
1562+
) :: iodata
1563+
def datetime_to_iodata(
1564+
year,
1565+
month,
1566+
day,
1567+
hour,
1568+
minute,
1569+
second,
1570+
microsecond,
1571+
time_zone,
1572+
zone_abbr,
1573+
utc_offset,
1574+
std_offset,
1575+
format \\ :extended
13971576
)
13981577
when is_time_zone(time_zone) and is_zone_abbr(zone_abbr) and is_utc_offset(utc_offset) and
13991578
is_std_offset(std_offset) do
1400-
date_to_string(year, month, day, format) <>
1401-
" " <>
1402-
time_to_string(hour, minute, second, microsecond, format) <>
1403-
offset_to_string(utc_offset, std_offset, time_zone, format) <>
1404-
zone_to_string(utc_offset, std_offset, zone_abbr, time_zone)
1579+
[
1580+
date_to_iodata(year, month, day, format),
1581+
?\s,
1582+
time_to_iodata(hour, minute, second, microsecond, format),
1583+
offset_to_iodata(utc_offset, std_offset, time_zone, format),
1584+
zone_to_iodata(utc_offset, std_offset, zone_abbr, time_zone)
1585+
]
14051586
end
14061587

14071588
@doc false
14081589
def offset_to_string(0, 0, "Etc/UTC", _format), do: "Z"
14091590

1410-
def offset_to_string(utc, std, _zone, format) do
1591+
def offset_to_string(utc, std, zone, format) do
1592+
offset_to_iodata(utc, std, zone, format)
1593+
|> IO.iodata_to_binary()
1594+
end
1595+
1596+
@doc false
1597+
def offset_to_iodata(0, 0, "Etc/UTC", _format), do: ?Z
1598+
1599+
def offset_to_iodata(utc, std, _zone, format) do
14111600
total = utc + std
14121601
second = abs(total)
14131602
minute = second |> rem(3600) |> div(60)
@@ -1416,15 +1605,15 @@ defmodule Calendar.ISO do
14161605
end
14171606

14181607
defp format_offset(total, hour, minute, :extended) do
1419-
sign(total) <> zero_pad(hour, 2) <> ":" <> zero_pad(minute, 2)
1608+
[sign(total), zero_pad(hour, 2), ?: | zero_pad(minute, 2)]
14201609
end
14211610

14221611
defp format_offset(total, hour, minute, :basic) do
1423-
sign(total) <> zero_pad(hour, 2) <> zero_pad(minute, 2)
1612+
[sign(total), zero_pad(hour, 2) | zero_pad(minute, 2)]
14241613
end
14251614

1426-
defp zone_to_string(_, _, _, "Etc/UTC"), do: ""
1427-
defp zone_to_string(_, _, abbr, zone), do: " " <> abbr <> " " <> zone
1615+
defp zone_to_iodata(_, _, _, "Etc/UTC"), do: []
1616+
defp zone_to_iodata(_, _, abbr, zone), do: [?\s, abbr, ?\s | zone]
14281617

14291618
@doc """
14301619
Determines if the date given is valid according to the proleptic Gregorian calendar.
@@ -1485,16 +1674,24 @@ defmodule Calendar.ISO do
14851674
{0, 1}
14861675
end
14871676

1488-
defp sign(total) when total < 0, do: "-"
1489-
defp sign(_), do: "+"
1677+
defp sign(total) when total < 0, do: ?-
1678+
defp sign(_), do: ?+
14901679

1491-
defp zero_pad(val, count) when val >= 0 do
1680+
defp zero_pad(val, count) when val >= 0 and count <= 6 do
14921681
num = Integer.to_string(val)
1493-
:binary.copy("0", max(count - byte_size(num), 0)) <> num
1682+
1683+
case max(count - byte_size(num), 0) do
1684+
0 -> num
1685+
1 -> ["0" | num]
1686+
2 -> ["00" | num]
1687+
3 -> ["000" | num]
1688+
4 -> ["0000" | num]
1689+
5 -> ["00000" | num]
1690+
end
14941691
end
14951692

14961693
defp zero_pad(val, count) do
1497-
"-" <> zero_pad(-val, count)
1694+
[?- | zero_pad(-val, count)]
14981695
end
14991696

15001697
@doc """

0 commit comments

Comments
 (0)