/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.beam.runners.dataflow.util; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; import org.joda.time.DateTime; import org.joda.time.Duration; import org.joda.time.Instant; import org.joda.time.ReadableDuration; import org.joda.time.ReadableInstant; import org.joda.time.chrono.ISOChronology; /** * A helper class for converting between Dataflow API and SDK time * representations. * * <p>Dataflow API times are strings of the form * {@code YYYY-MM-dd'T'HH:mm:ss[.nnnn]'Z'}: that is, RFC 3339 * strings with optional fractional seconds and a 'Z' offset. * * <p>Dataflow API durations are strings of the form {@code ['-']sssss[.nnnn]'s'}: * that is, seconds with optional fractional seconds and a literal 's' at the end. * * <p>In both formats, fractional seconds are either three digits (millisecond * resolution), six digits (microsecond resolution), or nine digits (nanosecond * resolution). */ public final class TimeUtil { private TimeUtil() {} // Non-instantiable. private static final Pattern DURATION_PATTERN = Pattern.compile("(\\d+)(?:\\.(\\d+))?s"); private static final Pattern TIME_PATTERN = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?Z"); /** * Converts a {@link ReadableInstant} into a Dateflow API time value. */ public static String toCloudTime(ReadableInstant instant) { // Note that since Joda objects use millisecond resolution, we always // produce either no fractional seconds or fractional seconds with // millisecond resolution. // Translate the ReadableInstant to a DateTime with ISOChronology. DateTime time = new DateTime(instant); int millis = time.getMillisOfSecond(); if (millis == 0) { return String.format("%04d-%02d-%02dT%02d:%02d:%02dZ", time.getYear(), time.getMonthOfYear(), time.getDayOfMonth(), time.getHourOfDay(), time.getMinuteOfHour(), time.getSecondOfMinute()); } else { return String.format("%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", time.getYear(), time.getMonthOfYear(), time.getDayOfMonth(), time.getHourOfDay(), time.getMinuteOfHour(), time.getSecondOfMinute(), millis); } } /** * Converts a time value received via the Dataflow API into the corresponding * {@link Instant}. * @return the parsed time, or null if a parse error occurs */ @Nullable public static Instant fromCloudTime(String time) { Matcher matcher = TIME_PATTERN.matcher(time); if (!matcher.matches()) { return null; } int year = Integer.valueOf(matcher.group(1)); int month = Integer.valueOf(matcher.group(2)); int day = Integer.valueOf(matcher.group(3)); int hour = Integer.valueOf(matcher.group(4)); int minute = Integer.valueOf(matcher.group(5)); int second = Integer.valueOf(matcher.group(6)); int millis = 0; String frac = matcher.group(7); if (frac != null) { int fracs = Integer.valueOf(frac); if (frac.length() == 3) { // millisecond resolution millis = fracs; } else if (frac.length() == 6) { // microsecond resolution millis = fracs / 1000; } else if (frac.length() == 9) { // nanosecond resolution millis = fracs / 1000000; } else { return null; } } return new DateTime(year, month, day, hour, minute, second, millis, ISOChronology.getInstanceUTC()).toInstant(); } /** * Converts a {@link ReadableDuration} into a Dataflow API duration string. */ public static String toCloudDuration(ReadableDuration duration) { // Note that since Joda objects use millisecond resolution, we always // produce either no fractional seconds or fractional seconds with // millisecond resolution. long millis = duration.getMillis(); long seconds = millis / 1000; millis = millis % 1000; if (millis == 0) { return String.format("%ds", seconds); } else { return String.format("%d.%03ds", seconds, millis); } } /** * Converts a Dataflow API duration string into a {@link Duration}. * @return the parsed duration, or null if a parse error occurs */ @Nullable public static Duration fromCloudDuration(String duration) { Matcher matcher = DURATION_PATTERN.matcher(duration); if (!matcher.matches()) { return null; } long millis = Long.valueOf(matcher.group(1)) * 1000; String frac = matcher.group(2); if (frac != null) { long fracs = Long.valueOf(frac); if (frac.length() == 3) { // millisecond resolution millis += fracs; } else if (frac.length() == 6) { // microsecond resolution millis += fracs / 1000; } else if (frac.length() == 9) { // nanosecond resolution millis += fracs / 1000000; } else { return null; } } return Duration.millis(millis); } }