@@ -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