-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for SnakeYaml to parse timestamps correctly when timezone…
… is set or not (#5626) * - Create a new SnakeYaml construct for timestamp to be able to handle timezones. - Tests added. * - Create a new SnakeYaml construct for timestamp to be able to handle timezones. - Tests added. * Update CustomConstructYamlTimeStamp to consistently return a given date as a String.
- Loading branch information
Showing
3 changed files
with
129 additions
and
1 deletion.
There are no files selected for viewing
83 changes: 83 additions & 0 deletions
83
...ibase-standard/src/main/java/liquibase/parser/core/yaml/CustomConstructYamlTimestamp.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package liquibase.parser.core.yaml; | ||
|
||
import org.yaml.snakeyaml.constructor.SafeConstructor; | ||
import org.yaml.snakeyaml.error.YAMLException; | ||
import org.yaml.snakeyaml.nodes.Node; | ||
import org.yaml.snakeyaml.nodes.ScalarNode; | ||
|
||
import java.util.Calendar; | ||
import java.util.TimeZone; | ||
import java.util.regex.Matcher; | ||
import java.util.regex.Pattern; | ||
|
||
public class CustomConstructYamlTimestamp extends SafeConstructor.ConstructYamlTimestamp { | ||
private final static Pattern TIMESTAMP_REGEXP = Pattern.compile( | ||
"^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:(?:[Tt]|[ \t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \t]*(?:Z|([-+][0-9][0-9]?)(?::([0-9][0-9])?)?))?)?$"); | ||
private final static Pattern YMD_REGEXP = | ||
Pattern.compile("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)$"); | ||
|
||
private Calendar calendar; | ||
|
||
@Override | ||
public Object construct(Node node) { | ||
ScalarNode scalar = (ScalarNode) node; | ||
String nodeValue = scalar.getValue(); | ||
Matcher match = YMD_REGEXP.matcher(nodeValue); | ||
if (match.matches()) { | ||
String year_s = match.group(1); | ||
String month_s = match.group(2); | ||
String day_s = match.group(3); | ||
calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); | ||
calendar.clear(); | ||
calendar.set(Calendar.YEAR, Integer.parseInt(year_s)); | ||
// Java's months are zero-based... | ||
calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1); // x | ||
calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s)); | ||
return calendar.getTime(); | ||
} else { | ||
match = TIMESTAMP_REGEXP.matcher(nodeValue); | ||
if (!match.matches()) { | ||
throw new YAMLException("Unexpected timestamp: " + nodeValue); | ||
} | ||
String year_s = match.group(1); | ||
String month_s = match.group(2); | ||
String day_s = match.group(3); | ||
String hour_s = match.group(4); | ||
String min_s = match.group(5); | ||
// seconds and milliseconds | ||
String seconds = match.group(6); | ||
String millis = match.group(7); | ||
if (millis != null) { | ||
seconds = seconds + "." + millis; | ||
} | ||
double fractions = Double.parseDouble(seconds); | ||
int sec_s = (int) Math.round(Math.floor(fractions)); | ||
int usec = (int) Math.round((fractions - sec_s) * 1000); | ||
// timezone | ||
String timezoneh_s = match.group(8); | ||
String timezonem_s = match.group(9); | ||
TimeZone timeZone = null; | ||
if (timezoneh_s != null) { | ||
String time = timezonem_s != null ? ":" + timezonem_s : "00"; | ||
timeZone = TimeZone.getTimeZone("GMT" + timezoneh_s + time); | ||
calendar = Calendar.getInstance(timeZone); | ||
; | ||
} else { | ||
calendar = Calendar.getInstance(); | ||
} | ||
calendar.set(Calendar.YEAR, Integer.parseInt(year_s)); | ||
// Java's months are zero-based... | ||
calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1); | ||
calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s)); | ||
calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour_s)); | ||
calendar.set(Calendar.MINUTE, Integer.parseInt(min_s)); | ||
calendar.set(Calendar.SECOND, sec_s); | ||
calendar.set(Calendar.MILLISECOND, usec); | ||
if (timeZone == null) { | ||
return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(calendar.getTime()); | ||
} else { | ||
return calendar.getTime().toString(); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
...andard/src/test/groovy/liquibase/parser/core/yaml/CustomConstructYamlTimestampTest.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package liquibase.parser.core.yaml | ||
|
||
import org.yaml.snakeyaml.DumperOptions | ||
import org.yaml.snakeyaml.nodes.ScalarNode | ||
import org.yaml.snakeyaml.nodes.Tag | ||
import spock.lang.Specification | ||
|
||
class CustomConstructYamlTimestampTest extends Specification { | ||
def "validate timestamp is parsed correctly without specifying a timezone"() { | ||
given: | ||
def node = new ScalarNode(new Tag("tag:yaml.org,2002:timestamp"), "2018-03-09 08:41:31.000", null, null, DumperOptions.ScalarStyle.createStyle(new Character('\'' as char))) | ||
def customConstructYamlTimestamp = new CustomConstructYamlTimestamp() | ||
when: | ||
def returnedValue = customConstructYamlTimestamp.construct(node) | ||
|
||
then: | ||
returnedValue.toString() == "2018-03-09 08:41:31" | ||
} | ||
|
||
def "validate timestamp is parsed correctly specifying a timezone"() { | ||
given: | ||
TimeZone.setDefault(TimeZone.getTimeZone("PST")) | ||
def node = new ScalarNode(new Tag("tag:yaml.org,2002:timestamp"), "2018-03-09 08:41:31.000+08:00", null, null, DumperOptions.ScalarStyle.createStyle(new Character('\'' as char))) | ||
def customConstructYamlTimestamp = new CustomConstructYamlTimestamp() | ||
when: | ||
def returnedValue = customConstructYamlTimestamp.construct(node) | ||
|
||
then: | ||
returnedValue.toString() == "Thu Mar 08 16:41:31 PST 2018" | ||
} | ||
} |