Skip to content

Commit 0c3c1e3

Browse files
committed
Merge branch 'master' into JAVA-42035
2 parents 51a061a + 7eb8b7f commit 0c3c1e3

File tree

103 files changed

+1428
-41
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+1428
-41
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package com.baeldung.apache.avro;
2+
3+
import org.apache.avro.Conversion;
4+
import org.apache.avro.LogicalTypes;
5+
import org.apache.avro.Schema;
6+
import org.apache.avro.data.TimeConversions;
7+
import org.apache.avro.generic.GenericData;
8+
import org.apache.avro.generic.GenericDatumReader;
9+
import org.apache.avro.generic.GenericDatumWriter;
10+
import org.apache.avro.generic.GenericRecord;
11+
import org.apache.avro.io.DatumReader;
12+
import org.apache.avro.io.DatumWriter;
13+
import org.apache.avro.io.Decoder;
14+
import org.apache.avro.io.DecoderFactory;
15+
import org.apache.avro.io.Encoder;
16+
import org.apache.avro.io.EncoderFactory;
17+
import org.apache.commons.lang3.tuple.Pair;
18+
import org.junit.jupiter.api.Test;
19+
20+
import java.io.ByteArrayOutputStream;
21+
import java.io.IOException;
22+
import java.time.Instant;
23+
import java.time.LocalDate;
24+
import java.time.ZoneId;
25+
import java.util.Date;
26+
27+
import static org.junit.jupiter.api.Assertions.assertEquals;
28+
29+
public class SerializeAndDeserializeDateUnitTest {
30+
31+
@Test
32+
void whenSerializingDateWithLogicalType_thenDeserializesCorrectly() throws IOException {
33+
34+
LocalDate expectedDate = LocalDate.now();
35+
Instant expectedTimestamp = Instant.now();
36+
37+
byte[] serialized = serializeDateWithLogicalType(expectedDate, expectedTimestamp);
38+
Pair<LocalDate, Instant> deserialized = deserializeDateWithLogicalType(serialized);
39+
40+
assertEquals(expectedDate, deserialized.getLeft());
41+
42+
// This is perfectly valid when using logical types
43+
assertEquals(expectedTimestamp.toEpochMilli(), deserialized.getRight().toEpochMilli(),
44+
"Timestamps should match exactly at millisecond precision");
45+
}
46+
47+
@Test
48+
void whenSerializingWithConversionApi_thenDeserializesCorrectly() throws IOException {
49+
50+
LocalDate expectedDate = LocalDate.now();
51+
Instant expectedTimestamp = Instant.now();
52+
53+
byte[] serialized = serializeWithConversionApi(expectedDate, expectedTimestamp);
54+
Pair<LocalDate, Instant> deserialized = deserializeWithConversionApi(serialized);
55+
56+
assertEquals(expectedDate, deserialized.getLeft());
57+
assertEquals(expectedTimestamp.toEpochMilli(), deserialized.getRight().toEpochMilli(),
58+
"Timestamps should match at millisecond precision");
59+
}
60+
61+
@Test
62+
void whenSerializingLegacyDate_thenConvertsCorrectly() throws IOException {
63+
64+
Date legacyDate = new Date();
65+
LocalDate expectedLocalDate = legacyDate.toInstant()
66+
.atZone(ZoneId.systemDefault())
67+
.toLocalDate();
68+
69+
byte[] serialized = serializeLegacyDateAsModern(legacyDate);
70+
LocalDate deserialized = deserializeDateWithLogicalType(serialized).getKey();
71+
72+
assertEquals(expectedLocalDate, deserialized);
73+
}
74+
75+
public static Schema createDateSchema() {
76+
String schemaJson =
77+
"{"
78+
+ "\"type\": \"record\","
79+
+ "\"name\": \"DateRecord\","
80+
+ "\"fields\": ["
81+
+ " {\"name\": \"date\", \"type\": {\"type\": \"int\", \"logicalType\": \"date\"}},"
82+
+ " {\"name\": \"timestamp\", \"type\": {\"type\": \"long\", \"logicalType\": \"timestamp-millis\"}}"
83+
+ "]"
84+
+ "}";
85+
return new Schema.Parser().parse(schemaJson);
86+
}
87+
88+
public static byte[] serializeDateWithLogicalType(LocalDate date, Instant timestamp) throws IOException {
89+
Schema schema = createDateSchema();
90+
GenericRecord record = new GenericData.Record(schema);
91+
92+
// Convert LocalDate to days since epoch
93+
record.put("date", (int) date.toEpochDay());
94+
95+
// Convert Instant to milliseconds since epoch
96+
record.put("timestamp", timestamp.toEpochMilli());
97+
98+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
99+
DatumWriter<GenericRecord> datumWriter = new GenericDatumWriter<>(schema);
100+
Encoder encoder = EncoderFactory.get().binaryEncoder(baos, null);
101+
102+
datumWriter.write(record, encoder);
103+
encoder.flush();
104+
105+
return baos.toByteArray();
106+
}
107+
108+
public static Pair<LocalDate, Instant> deserializeDateWithLogicalType(byte[] bytes) throws IOException {
109+
Schema schema = createDateSchema();
110+
DatumReader<GenericRecord> datumReader = new GenericDatumReader<>(schema);
111+
Decoder decoder = DecoderFactory.get().binaryDecoder(bytes, null);
112+
113+
GenericRecord record = datumReader.read(null, decoder);
114+
115+
// Convert days since epoch back to LocalDate
116+
LocalDate date = LocalDate.ofEpochDay((int) record.get("date"));
117+
118+
// Convert milliseconds since epoch back to Instant
119+
Instant timestamp = Instant.ofEpochMilli((long) record.get("timestamp"));
120+
121+
return Pair.of(date, timestamp);
122+
}
123+
124+
public static byte[] serializeWithConversionApi(LocalDate date, Instant timestamp) throws IOException {
125+
Schema schema = createDateSchema();
126+
GenericRecord record = new GenericData.Record(schema);
127+
128+
// Use LogicalTypes.date() for conversion
129+
Conversion<LocalDate> dateConversion = new org.apache.avro.data.TimeConversions.DateConversion();
130+
LogicalTypes.date().addToSchema(schema.getField("date").schema());
131+
132+
// Use LogicalTypes.timestampMillis() for conversion
133+
Conversion<Instant> timestampConversion = new org.apache.avro.data.TimeConversions.TimestampMillisConversion();
134+
LogicalTypes.timestampMillis().addToSchema(schema.getField("timestamp").schema());
135+
136+
record.put("date", dateConversion.toInt(date, schema.getField("date").schema(), LogicalTypes.date()));
137+
record.put("timestamp", timestampConversion.toLong(timestamp, schema.getField("timestamp").schema(), LogicalTypes.timestampMillis()));
138+
139+
// Serialize as before
140+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
141+
DatumWriter<GenericRecord> datumWriter = new GenericDatumWriter<>(schema);
142+
Encoder encoder = EncoderFactory.get().binaryEncoder(baos, null);
143+
144+
datumWriter.write(record, encoder);
145+
encoder.flush();
146+
147+
return baos.toByteArray();
148+
}
149+
150+
public static Pair<LocalDate, Instant> deserializeWithConversionApi(byte[] bytes) throws IOException {
151+
Schema schema = createDateSchema();
152+
DatumReader<GenericRecord> datumReader = new GenericDatumReader<>(schema);
153+
Decoder decoder = DecoderFactory.get().binaryDecoder(bytes, null);
154+
155+
GenericRecord record = datumReader.read(null, decoder);
156+
157+
// Use LogicalTypes.date() for conversion
158+
Conversion<LocalDate> dateConversion = new TimeConversions.DateConversion();
159+
LogicalTypes.date().addToSchema(schema.getField("date").schema());
160+
161+
// Use LogicalTypes.timestampMillis() for conversion
162+
Conversion<Instant> timestampConversion = new TimeConversions.TimestampMillisConversion();
163+
LogicalTypes.timestampMillis().addToSchema(schema.getField("timestamp").schema());
164+
165+
// Get the primitive values from the record
166+
int daysSinceEpoch = (int) record.get("date");
167+
long millisSinceEpoch = (long) record.get("timestamp");
168+
169+
// Convert back to Java types using the conversion API
170+
LocalDate date = dateConversion.fromInt(
171+
daysSinceEpoch,
172+
schema.getField("date").schema(),
173+
LogicalTypes.date()
174+
);
175+
176+
Instant timestamp = timestampConversion.fromLong(
177+
millisSinceEpoch,
178+
schema.getField("timestamp").schema(),
179+
LogicalTypes.timestampMillis()
180+
);
181+
182+
return Pair.of(date, timestamp);
183+
}
184+
185+
public static byte[] serializeLegacyDateAsModern(Date legacyDate) throws IOException {
186+
// Convert java.util.Date to java.time.Instant
187+
Instant instant = legacyDate.toInstant();
188+
189+
// Convert to LocalDate if you need date-only information
190+
LocalDate localDate = instant.atZone(ZoneId.systemDefault()).toLocalDate();
191+
192+
// Then use one of our modern date serialization methods
193+
return serializeDateWithLogicalType(localDate, instant);
194+
}
195+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#core-java-currency
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<artifactId>core-java-currency</artifactId>
7+
<packaging>jar</packaging>
8+
<name>core-java-currency</name>
9+
10+
<parent>
11+
<groupId>com.baeldung.core-java-modules</groupId>
12+
<artifactId>core-java-modules</artifactId>
13+
<version>0.0.1-SNAPSHOT</version>
14+
</parent>
15+
16+
<dependencies>
17+
<dependency>
18+
<groupId>org.projectlombok</groupId>
19+
<artifactId>lombok</artifactId>
20+
<version>${lombok.version}</version>
21+
</dependency>
22+
</dependencies>
23+
24+
<build>
25+
<plugins>
26+
<plugin>
27+
<groupId>org.apache.maven.plugins</groupId>
28+
<artifactId>maven-surefire-plugin</artifactId>
29+
<version>${maven-surefire-plugin.version}</version>
30+
<configuration>
31+
<argLine>--enable-preview</argLine>
32+
</configuration>
33+
</plugin>
34+
</plugins>
35+
</build>
36+
37+
<properties>
38+
<maven.compiler.source.version>17</maven.compiler.source.version>
39+
<maven.compiler.target.version>17</maven.compiler.target.version>
40+
<lombok.version>1.18.24</lombok.version>
41+
</properties>
42+
43+
</project>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.baeldung.currency.utils;
2+
3+
import java.util.Currency;
4+
import java.util.Locale;
5+
6+
public class CurrencyLocaleUtil {
7+
public String getSymbolForLocale(Locale locale) {
8+
Currency currency = Currency.getInstance(locale);
9+
return currency.getSymbol();
10+
}
11+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.baeldung.currency.utils;
2+
3+
import java.util.Map;
4+
5+
public class CurrencyMapUtil {
6+
private static final Map<String, String> currencymap = Map.of(
7+
"USD", "$",
8+
"EUR", "€",
9+
"INR", "₹"
10+
);
11+
12+
public static String getSymbol(String currencyCode) {
13+
return currencymap.getOrDefault(currencyCode, "Unknown");
14+
}
15+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.baeldung.currency.utils;
2+
3+
import java.util.Currency;
4+
5+
public class CurrencyUtil {
6+
public static String getSymbol(String currencyCode) {
7+
Currency currency = Currency.getInstance(currencyCode);
8+
return currency.getSymbol();
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.baeldung.currency.utils;
2+
3+
import org.junit.jupiter.api.Test;
4+
import java.util.Locale;
5+
import static org.junit.jupiter.api.Assertions.*;
6+
7+
class CurrencyLocaleUtilTest {
8+
private final CurrencyLocaleUtil currencyLocale = new CurrencyLocaleUtil();
9+
10+
@Test
11+
void givenLocale_whenGetSymbolForLocale_thenReturnsLocalizedSymbol() {
12+
assertEquals("$", currencyLocale.getSymbolForLocale(Locale.US));
13+
assertEquals("€", currencyLocale.getSymbolForLocale(Locale.FRANCE));
14+
}
15+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.baeldung.currency.utils;
2+
3+
import org.junit.jupiter.api.Test;
4+
import static org.junit.jupiter.api.Assertions.*;
5+
6+
class CurrencyMapUtilTest {
7+
@Test
8+
void givenValidCurrencyCode_whenGetSymbol_thenReturnsCorrectSymbol() {
9+
assertEquals("$", CurrencyMapUtil.getSymbol("USD"));
10+
assertEquals("€", CurrencyMapUtil.getSymbol("EUR"));
11+
assertEquals("₹", CurrencyMapUtil.getSymbol("INR"));
12+
}
13+
14+
@Test
15+
void givenInvalidCurrencyCode_whenGetSymbol_thenReturnsUnknown() {
16+
assertEquals("Unknown", CurrencyMapUtil.getSymbol("XYZ"));
17+
}
18+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.baeldung.currency.utils;
2+
3+
import org.junit.jupiter.api.Test;
4+
import static org.junit.jupiter.api.Assertions.*;
5+
6+
class CurrencyUtilTest {
7+
@Test
8+
void givenValidCurrencyCode_whenGetSymbol_thenReturnsCorrectSymbol() {
9+
assertEquals("$", CurrencyUtil.getSymbol("USD"));
10+
assertEquals("€", CurrencyUtil.getSymbol("EUR"));
11+
}
12+
13+
@Test
14+
void givenInvalidCurrencyCode_whenGetSymbol_thenThrowsException() {
15+
assertThrows(IllegalArgumentException.class, () -> CurrencyUtil.getSymbol("INVALID"));
16+
}
17+
}

core-java-modules/core-java-date-operations/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@
4949
</build>
5050

5151
<properties>
52-
<joda-time.version>2.12.5</joda-time.version>
52+
<joda-time.version>2.13.1</joda-time.version>
5353
<hirondelle-date4j.version>RELEASE</hirondelle-date4j.version>
5454
</properties>
5555

56-
</project>
56+
</project>

0 commit comments

Comments
 (0)