Repository: googleapis/java-storage
Pull request #1394 (2022-05-16 17:49:33)

feat: refactor model classes to prefer to java.time types
### What's been added

Fields which map to apiary DateTime RFC 3339 fields will now also have
a preferred java.time.OffsetDateTime getter & setter.

Fields which map to apiary long to represent a duration in epoch millis
will no also have a preferred java.time.Duration getter & setter.

All new methods currently have the name of the corresponding java.time
type appended to the property name.

#### Refactor Long -> java.time.OffsetDateTime
* BlobInfo#createTime
* BlobInfo#customTime
* BlobInfo#deleteTime
* BlobInfo#retentionExpirationTime
* BlobInfo#timeStorageClassUpdated
* BlobInfo#updateTime
* Blob#createTime
* Blob#customTime
* Blob#deleteTime
* Blob#retentionExpirationTime
* Blob#timeStorageClassUpdated
* Blob#updateTime
* BucketInfo.CreatedBeforeDeleteRule
* BucketInfo.DeleteRule
* BucketInfo.IsLiveDeleteRule
* BucketInfo.NumNewerVersionsDeleteRule
* BucketInfo.RawDeleteRule
* BucketInfo#createTime
* BucketInfo#updateTime
* Bucket#createTime
* Bucket#updateTime
* Bucket#retentionEffectiveTime
* HmacKey.HmacKeyMetadata#createTime
* HmacKey.HmacKeyMetadata#updateTime

#### Refactor Long -> java.time.Duration
* BucketInfo#retentionPeriod
* Bucket#retentionPeriod

### Deprecation of existing methods
All existing "Long" based methods have been marked as `@Deprecated`
and had their javadocs updated to point to the new preferred
corresponding java.time method.

When the existing "Long" based methods are removed, the corresponding
java.time method will replace it at which point the java.time suffix
methods will be deprecated for later cleanup.

All existing "Long" based methods take care of performing the necessary
conversion to the respective java.time type and will continue to work
until their removal.

##### Why append the java.time type as a suffix?
1. Getters can't be overloaded, so we already need something to disambiguate
   which type should be returned.
2. For setters: we theoretically could overload since we have different 
   method arguments, however, if a customer is already passing `null` as the
   method argument we've now created an ambiguity as to which method to
   invoke. By suffixing the setters as well, we avoid creating this possible
   ambiguity.

Part 2 of https://github.com/googleapis/java-storage/pull/1388


Patch:
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java
index 048522bd..a8249643 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java
@@ -16,6 +16,7 @@
 
 package com.google.cloud.storage;
 
+import static com.google.cloud.storage.Utils.durationMillisCodec;
 import static com.google.cloud.storage.Utils.ifNonNull;
 import static com.google.cloud.storage.Utils.lift;
 import static com.google.cloud.storage.Utils.toImmutableListOf;
@@ -47,14 +48,18 @@ import com.google.cloud.storage.Acl.RawEntity;
 import com.google.cloud.storage.Acl.Role;
 import com.google.cloud.storage.Acl.User;
 import com.google.cloud.storage.BlobInfo.CustomerEncryption;
+import com.google.cloud.storage.BucketInfo.AgeDeleteRule;
 import com.google.cloud.storage.BucketInfo.BuilderImpl;
+import com.google.cloud.storage.BucketInfo.CreatedBeforeDeleteRule;
 import com.google.cloud.storage.BucketInfo.IamConfiguration;
+import com.google.cloud.storage.BucketInfo.IsLiveDeleteRule;
 import com.google.cloud.storage.BucketInfo.LifecycleRule;
 import com.google.cloud.storage.BucketInfo.LifecycleRule.DeleteLifecycleAction;
 import com.google.cloud.storage.BucketInfo.LifecycleRule.LifecycleAction;
 import com.google.cloud.storage.BucketInfo.LifecycleRule.LifecycleCondition;
 import com.google.cloud.storage.BucketInfo.LifecycleRule.SetStorageClassLifecycleAction;
 import com.google.cloud.storage.BucketInfo.Logging;
+import com.google.cloud.storage.BucketInfo.NumNewerVersionsDeleteRule;
 import com.google.cloud.storage.BucketInfo.PublicAccessPrevention;
 import com.google.cloud.storage.BucketInfo.RawDeleteRule;
 import com.google.cloud.storage.Conversions.Codec;
@@ -79,6 +84,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
 
 @InternalApi
 final class ApiaryConversions {
@@ -122,8 +129,13 @@ final class ApiaryConversions {
       notificationInfoCodec = Codec.of(this::notificationEncode, this::notificationDecode);
 
   @VisibleForTesting
-  final Codec<DateTime, OffsetDateTime> dateTimeCodec =
+  final Codec<OffsetDateTime, DateTime> dateTimeCodec =
       Codec.of(
+          odt -> {
+            ZoneOffset offset = odt.getOffset();
+            int i = Math.toIntExact(TimeUnit.SECONDS.toMinutes(offset.getTotalSeconds()));
+            return new DateTime(odt.toInstant().toEpochMilli(), i);
+          },
           dt -> {
             long milli = dt.getValue();
             int timeZoneShiftMinutes = dt.getTimeZoneShift();
@@ -137,11 +149,6 @@ final class ApiaryConversions {
             ZoneOffset offset = ZoneOffset.ofHoursMinutes(hours, minutes);
 
             return Instant.ofEpochMilli(milli).atOffset(offset);
-          },
-          odt -> {
-            ZoneOffset offset = odt.getOffset();
-            int i = Math.toIntExact(TimeUnit.SECONDS.toMinutes(offset.getTotalSeconds()));
-            return new DateTime(odt.toInstant().toEpochMilli(), i);
           });
 
   private ApiaryConversions() {}
@@ -214,10 +221,10 @@ final class ApiaryConversions {
   private StorageObject blobInfoEncode(BlobInfo from) {
     StorageObject to = blobIdEncode(from.getBlobId());
     ifNonNull(from.getAcl(), toImmutableListOf(objectAcl()::encode), to::setAcl);
-    ifNonNull(from.getDeleteTimeOffsetDateTime(), dateTimeCodec::decode, to::setTimeDeleted);
-    ifNonNull(from.getUpdateTimeOffsetDateTime(), dateTimeCodec::decode, to::setUpdated);
-    ifNonNull(from.getCreateTimeOffsetDateTime(), dateTimeCodec::decode, to::setTimeCreated);
-    ifNonNull(from.getCustomTimeOffsetDateTime(), dateTimeCodec::decode, to::setCustomTime);
+    ifNonNull(from.getDeleteTimeOffsetDateTime(), dateTimeCodec::encode, to::setTimeDeleted);
+    ifNonNull(from.getUpdateTimeOffsetDateTime(), dateTimeCodec::encode, to::setUpdated);
+    ifNonNull(from.getCreateTimeOffsetDateTime(), dateTimeCodec::encode, to::setTimeCreated);
+    ifNonNull(from.getCustomTimeOffsetDateTime(), dateTimeCodec::encode, to::setCustomTime);
     ifNonNull(from.getSize(), BigInteger::valueOf, to::setSize);
     ifNonNull(
         from.getOwner(),
@@ -226,13 +233,13 @@ final class ApiaryConversions {
     ifNonNull(from.getStorageClass(), StorageClass::toString, to::setStorageClass);
     ifNonNull(
         from.getTimeStorageClassUpdatedOffsetDateTime(),
-        dateTimeCodec::decode,
+        dateTimeCodec::encode,
         to::setTimeStorageClassUpdated);
     ifNonNull(
         from.getCustomerEncryption(), this::customerEncryptionEncode, to::setCustomerEncryption);
     ifNonNull(
         from.getRetentionExpirationTimeOffsetDateTime(),
-        dateTimeCodec::decode,
+        dateTimeCodec::encode,
         to::setRetentionExpirationTime);
     to.setKmsKeyName(from.getKmsKeyName());
     to.setEventBasedHold(from.getEventBasedHold());
@@ -278,10 +285,10 @@ final class ApiaryConversions {
     ifNonNull(from.getId(), to::setGeneratedId);
     ifNonNull(from.getSelfLink(), to::setSelfLink);
     ifNonNull(from.getMetadata(), to::setMetadata);
-    ifNonNull(from.getTimeDeleted(), dateTimeCodec::encode, to::setDeleteTime);
-    ifNonNull(from.getUpdated(), dateTimeCodec::encode, to::setUpdateTime);
-    ifNonNull(from.getTimeCreated(), dateTimeCodec::encode, to::setCreateTime);
-    ifNonNull(from.getCustomTime(), dateTimeCodec::encode, to::setCustomTime);
+    ifNonNull(from.getTimeDeleted(), dateTimeCodec::decode, to::setDeleteTimeOffsetDateTime);
+    ifNonNull(from.getUpdated(), dateTimeCodec::decode, to::setUpdateTimeOffsetDateTime);
+    ifNonNull(from.getTimeCreated(), dateTimeCodec::decode, to::setCreateTimeOffsetDateTime);
+    ifNonNull(from.getCustomTime(), dateTimeCodec::decode, to::setCustomTimeOffsetDateTime);
     ifNonNull(from.getSize(), BigInteger::longValue, to::setSize);
     ifNonNull(from.getOwner(), lift(Owner::getEntity).andThen(this::entityDecode), to::setOwner);
     ifNonNull(from.getAcl(), toImmutableListOf(objectAcl()::decode), to::setAcl);
@@ -292,12 +299,16 @@ final class ApiaryConversions {
         from.getCustomerEncryption(), this::customerEncryptionDecode, to::setCustomerEncryption);
     ifNonNull(from.getStorageClass(), StorageClass::valueOf, to::setStorageClass);
     ifNonNull(
-        from.getTimeStorageClassUpdated(), dateTimeCodec::encode, to::setTimeStorageClassUpdated);
+        from.getTimeStorageClassUpdated(),
+        dateTimeCodec::decode,
+        to::setTimeStorageClassUpdatedOffsetDateTime);
     ifNonNull(from.getKmsKeyName(), to::setKmsKeyName);
     ifNonNull(from.getEventBasedHold(), to::setEventBasedHold);
     ifNonNull(from.getTemporaryHold(), to::setTemporaryHold);
     ifNonNull(
-        from.getRetentionExpirationTime(), dateTimeCodec::encode, to::setRetentionExpirationTime);
+        from.getRetentionExpirationTime(),
+        dateTimeCodec::decode,
+        to::setRetentionExpirationTimeOffsetDateTime);
     return to.build();
   }
 
@@ -327,7 +338,7 @@ final class ApiaryConversions {
     Bucket to = new Bucket();
     ifNonNull(from.getAcl(), toImmutableListOf(bucketAcl()::encode), to::setAcl);
     ifNonNull(from.getCors(), toImmutableListOf(cors()::encode), to::setCors);
-    ifNonNull(from.getCreateTimeOffsetDateTime(), dateTimeCodec::decode, to::setTimeCreated);
+    ifNonNull(from.getCreateTimeOffsetDateTime(), dateTimeCodec::encode, to::setTimeCreated);
     ifNonNull(
         from.getDefaultAcl(), toImmutableListOf(objectAcl()::encode), to::setDefaultObjectAcl);
     ifNonNull(from.getLocation(), to::setLocation);
@@ -339,7 +350,7 @@ final class ApiaryConversions {
         to::setOwner);
     ifNonNull(from.getRpo(), Rpo::toString, to::setRpo);
     ifNonNull(from.getStorageClass(), StorageClass::toString, to::setStorageClass);
-    ifNonNull(from.getUpdateTimeOffsetDateTime(), dateTimeCodec::decode, to::setUpdated);
+    ifNonNull(from.getUpdateTimeOffsetDateTime(), dateTimeCodec::encode, to::setUpdated);
     ifNonNull(from.versioningEnabled(), b -> new Versioning().setEnabled(b), to::setVersioning);
     to.setEtag(from.getEtag());
     to.setId(from.getGeneratedId());
@@ -391,19 +402,18 @@ final class ApiaryConversions {
         k -> new Encryption().setDefaultKmsKeyName(k),
         to::setEncryption);
     ifNonNull(from.getLabels(), to::setLabels);
-    if (from.getRetentionPeriod() != null) {
-      if (Data.isNull(from.getRetentionPeriod())) {
-        to.setRetentionPolicy(Data.nullOf(Bucket.RetentionPolicy.class));
-      } else {
-        Bucket.RetentionPolicy retentionPolicy = new Bucket.RetentionPolicy();
-        retentionPolicy.setRetentionPeriod(from.getRetentionPeriod());
-        ifNonNull(
-            from.getRetentionEffectiveTimeOffsetDateTime(),
-            dateTimeCodec::decode,
-            retentionPolicy::setEffectiveTime);
-        ifNonNull(from.retentionPolicyIsLocked(), retentionPolicy::setIsLocked);
-        to.setRetentionPolicy(retentionPolicy);
-      }
+    Duration retentionPeriod = from.getRetentionPeriodDuration();
+    if (retentionPeriod == null) {
+      to.setRetentionPolicy(Data.nullOf(Bucket.RetentionPolicy.class));
+    } else {
+      Bucket.RetentionPolicy retentionPolicy = new Bucket.RetentionPolicy();
+      retentionPolicy.setRetentionPeriod(durationMillisCodec.encode(retentionPeriod));
+      ifNonNull(
+          from.getRetentionEffectiveTimeOffsetDateTime(),
+          dateTimeCodec::encode,
+          retentionPolicy::setEffectiveTime);
+      ifNonNull(from.retentionPolicyIsLocked(), retentionPolicy::setIsLocked);
+      to.setRetentionPolicy(retentionPolicy);
     }
     ifNonNull(from.getIamConfiguration(), this::iamConfigEncode, to::setIamConfiguration);
     ifNonNull(from.getLogging(), this::loggingEncode, to::setLogging);
@@ -427,8 +437,8 @@ final class ApiaryConversions {
     ifNonNull(from.getRpo(), Rpo::valueOf, to::setRpo);
     ifNonNull(from.getSelfLink(), to::setSelfLink);
     ifNonNull(from.getStorageClass(), StorageClass::valueOf, to::setStorageClass);
-    ifNonNull(from.getTimeCreated(), dateTimeCodec::encode, to::setCreateTime);
-    ifNonNull(from.getUpdated(), dateTimeCodec::encode, to::setUpdateTime);
+    ifNonNull(from.getTimeCreated(), dateTimeCodec::decode, to::setCreateTimeOffsetDateTime);
+    ifNonNull(from.getUpdated(), dateTimeCodec::decode, to::setUpdateTimeOffsetDateTime);
     ifNonNull(from.getVersioning(), Versioning::getEnabled, to::setVersioningEnabled);
     ifNonNull(from.getWebsite(), Website::getMainPageSuffix, to::setIndexPage);
     ifNonNull(from.getWebsite(), Website::getNotFoundPage, to::setNotFoundPage);
@@ -453,7 +463,8 @@ final class ApiaryConversions {
 
     RetentionPolicy retentionPolicy = from.getRetentionPolicy();
     if (retentionPolicy != null && retentionPolicy.getEffectiveTime() != null) {
-      to.setRetentionEffectiveTime(dateTimeCodec.encode(retentionPolicy.getEffectiveTime()));
+      to.setRetentionEffectiveTimeOffsetDateTime(
+          dateTimeCodec.decode(retentionPolicy.getEffectiveTime()));
     }
     ifNonNull(retentionPolicy, RetentionPolicy::getIsLocked, to::setRetentionPolicyIsLocked);
     ifNonNull(retentionPolicy, RetentionPolicy::getRetentionPeriod, to::setRetentionPeriod);
@@ -466,13 +477,40 @@ final class ApiaryConversions {
   @SuppressWarnings("deprecation")
   private Rule deleteRuleEncode(BucketInfo.DeleteRule from) {
     if (from instanceof RawDeleteRule) {
-      RawDeleteRule rule = (RawDeleteRule) from;
-      return rule.getRule();
+      RawDeleteRule raw = (RawDeleteRule) from;
+      Rule rule = raw.getRule();
+      String msg =
+          "The lifecycle condition "
+              + resolveRuleActionType(rule)
+              + " is not currently supported. Please update to the latest version of google-cloud-java."
+              + " Also, use LifecycleRule rather than the deprecated DeleteRule.";
+      // manually construct a log record, so we maintain class name and method name
+      // from the old implicit values.
+      LogRecord record = new LogRecord(Level.WARNING, msg);
+      record.setSourceClassName(BucketInfo.RawDeleteRule.class.getName());
+      record.setSourceMethodName("populateCondition");
+      BucketInfo.log.log(record);
+      return rule;
     }
     Rule to = new Rule();
     to.setAction(new Rule.Action().setType(BucketInfo.DeleteRule.SUPPORTED_ACTION));
     Rule.Condition condition = new Rule.Condition();
-    from.populateCondition(condition);
+    if (from instanceof CreatedBeforeDeleteRule) {
+      CreatedBeforeDeleteRule r = (CreatedBeforeDeleteRule) from;
+      ifNonNull(
+          r.getTime(),
+          lift(dateTimeCodec::encode).andThen(this::truncateToDateWithNoTzDrift),
+          condition::setCreatedBefore);
+    } else if (from instanceof AgeDeleteRule) {
+      AgeDeleteRule r = (AgeDeleteRule) from;
+      condition.setAge(r.getDaysToLive());
+    } else if (from instanceof NumNewerVersionsDeleteRule) {
+      NumNewerVersionsDeleteRule r = (NumNewerVersionsDeleteRule) from;
+      condition.setNumNewerVersions(r.getNumNewerVersions());
+    } else if (from instanceof IsLiveDeleteRule) {
+      IsLiveDeleteRule r = (IsLiveDeleteRule) from;
+      condition.setIsLive(r.isLive());
+    } // else would be RawDeleteRule which is handled above
     to.setCondition(condition);
     return to;
   }
@@ -480,7 +518,7 @@ final class ApiaryConversions {
   @SuppressWarnings("deprecation")
   private BucketInfo.DeleteRule deleteRuleDecode(Rule from) {
     if (from.getAction() != null
-        && BucketInfo.DeleteRule.SUPPORTED_ACTION.endsWith(from.getAction().getType())) {
+        && BucketInfo.DeleteRule.SUPPORTED_ACTION.endsWith(resolveRuleActionType(from))) {
       Rule.Condition condition = from.getCondition();
       Integer age = condition.getAge();
       if (age != null) {
@@ -488,7 +526,7 @@ final class ApiaryConversions {
       }
       DateTime dateTime = condition.getCreatedBefore();
       if (dateTime != null) {
-        return new BucketInfo.CreatedBeforeDeleteRule(dateTime.getValue());
+        return new BucketInfo.CreatedBeforeDeleteRule(dateTimeCodec.decode(dateTime));
       }
       Integer numNewerVersions = condition.getNumNewerVersions();
       if (numNewerVersions != null) {
@@ -518,7 +556,9 @@ final class ApiaryConversions {
     IamConfiguration.Builder to =
         IamConfiguration.newBuilder().setIsUniformBucketLevelAccessEnabled(ubla.getEnabled());
     ifNonNull(
-        ubla.getLockedTime(), dateTimeCodec::encode, to::setUniformBucketLevelAccessLockedTime);
+        ubla.getLockedTime(),
+        dateTimeCodec::decode,
+        to::setUniformBucketLevelAccessLockedTimeOffsetDateTime);
     ifNonNull(
         from.getPublicAccessPrevention(),
         PublicAccessPrevention::parse,
@@ -531,7 +571,7 @@ final class ApiaryConversions {
     to.setEnabled(from.isUniformBucketLevelAccessEnabled());
     ifNonNull(
         from.getUniformBucketLevelAccessLockedTimeOffsetDateTime(),
-        dateTimeCodec::decode,
+        dateTimeCodec::encode,
         to::setLockedTime);
     return to;
   }
@@ -695,8 +735,8 @@ final class ApiaryConversions {
     to.setProjectId(from.getProjectId());
     ifNonNull(from.getServiceAccount(), ServiceAccount::getEmail, to::setServiceAccountEmail);
     ifNonNull(from.getState(), Object::toString, to::setState);
-    ifNonNull(from.getCreateTime(), DateTime::new, to::setTimeCreated);
-    ifNonNull(from.getUpdateTime(), DateTime::new, to::setUpdated);
+    ifNonNull(from.getCreateTimeOffsetDateTime(), dateTimeCodec::encode, to::setTimeCreated);
+    ifNonNull(from.getUpdateTimeOffsetDateTime(), dateTimeCodec::encode, to::setUpdated);
     return to;
   }
 
@@ -704,12 +744,12 @@ final class ApiaryConversions {
       com.google.api.services.storage.model.HmacKeyMetadata from) {
     return HmacKeyMetadata.newBuilder(ServiceAccount.of(from.getServiceAccountEmail()))
         .setAccessId(from.getAccessId())
-        .setCreateTime(from.getTimeCreated().getValue())
+        .setCreateTimeOffsetDateTime(dateTimeCodec.decode(from.getTimeCreated()))
         .setEtag(from.getEtag())
         .setId(from.getId())
         .setProjectId(from.getProjectId())
         .setState(HmacKeyState.valueOf(from.getState()))
-        .setUpdateTime(from.getUpdated().getValue())
+        .setUpdateTimeOffsetDateTime(dateTimeCodec.decode(from.getUpdated()))
         .build();
   }
 
@@ -838,4 +878,12 @@ final class ApiaryConversions {
   private DateTime truncateToDateWithNoTzDrift(DateTime dt) {
     return new DateTime(true, dt.getValue(), 0);
   }
+
+  private String resolveRuleActionType(Rule rule) {
+    if (rule != null && rule.getAction() != null) {
+      return rule.getAction().getType();
+    } else {
+      return null;
+    }
+  }
 }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BackwardCompatibilityUtils.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BackwardCompatibilityUtils.java
index e4674b38..7dd80f21 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BackwardCompatibilityUtils.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BackwardCompatibilityUtils.java
@@ -19,6 +19,7 @@ package com.google.cloud.storage;
 import static java.util.Objects.requireNonNull;
 
 import com.google.cloud.storage.Conversions.Codec;
+import java.time.Duration;
 import java.time.Instant;
 import java.time.OffsetDateTime;
 import java.time.ZoneOffset;
@@ -37,11 +38,21 @@ final class BackwardCompatibilityUtils {
   // would.
   static final Codec<@Nullable Long, @Nullable OffsetDateTime> millisOffsetDateTimeCodec =
       Codec.<Long, OffsetDateTime>of(
-              m ->
-                  Instant.ofEpochMilli(requireNonNull(m, "m must be non null"))
+              l ->
+                  Instant.ofEpochMilli(requireNonNull(l, "l must be non null"))
                       .atOffset(ZoneOffset.systemDefault().getRules().getOffset(Instant.now())),
               odt -> requireNonNull(odt, "odt must be non null").toInstant().toEpochMilli())
           .nullable();
 
+  static final Codec<Long, OffsetDateTime> millisUtcCodec =
+      Codec.of(
+          l ->
+              Instant.ofEpochMilli(requireNonNull(l, "l must be non null"))
+                  .atOffset(ZoneOffset.UTC),
+          odt -> requireNonNull(odt, "odt must be non null").toInstant().toEpochMilli());
+
+  static final Codec<@Nullable Duration, @Nullable Long> nullableDurationMillisCodec =
+      Utils.durationMillisCodec.nullable();
+
   private BackwardCompatibilityUtils() {}
 }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java
index 14b0885f..ced9cf94 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java
@@ -37,6 +37,7 @@ import java.io.OutputStream;
 import java.net.URL;
 import java.nio.file.Path;
 import java.security.Key;
+import java.time.OffsetDateTime;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -384,42 +385,82 @@ public class Blob extends BlobInfo {
       return this;
     }
 
+    /** @deprecated Use {@link #setTimeStorageClassUpdatedOffsetDateTime(OffsetDateTime)} */
     @Override
+    @Deprecated
     public Builder setTimeStorageClassUpdated(Long timeStorageClassUpdated) {
       infoBuilder.setTimeStorageClassUpdated(timeStorageClassUpdated);
       return this;
     }
 
+    @Override
+    public BlobInfo.Builder setTimeStorageClassUpdatedOffsetDateTime(
+        OffsetDateTime timeStorageClassUpdated) {
+      infoBuilder.setTimeStorageClassUpdatedOffsetDateTime(timeStorageClassUpdated);
+      return this;
+    }
+
     @Override
     Builder setMetageneration(Long metageneration) {
       infoBuilder.setMetageneration(metageneration);
       return this;
     }
 
+    /** @deprecated Use {@link #setDeleteTimeOffsetDateTime(OffsetDateTime)} */
     @Override
+    @Deprecated
     Builder setDeleteTime(Long deleteTime) {
       infoBuilder.setDeleteTime(deleteTime);
       return this;
     }
 
     @Override
+    BlobInfo.Builder setDeleteTimeOffsetDateTime(OffsetDateTime deleteTime) {
+      infoBuilder.setDeleteTimeOffsetDateTime(deleteTime);
+      return this;
+    }
+
+    /** @deprecated Use {@link #setUpdateTimeOffsetDateTime(OffsetDateTime)} */
+    @Override
+    @Deprecated
     Builder setUpdateTime(Long updateTime) {
       infoBuilder.setUpdateTime(updateTime);
       return this;
     }
 
     @Override
+    BlobInfo.Builder setUpdateTimeOffsetDateTime(OffsetDateTime updateTime) {
+      infoBuilder.setUpdateTimeOffsetDateTime(updateTime);
+      return this;
+    }
+
+    @Override
+    @Deprecated
     Builder setCreateTime(Long createTime) {
       infoBuilder.setCreateTime(createTime);
       return this;
     }
 
     @Override
+    BlobInfo.Builder setCreateTimeOffsetDateTime(OffsetDateTime createTime) {
+      infoBuilder.setCreateTimeOffsetDateTime(createTime);
+      return this;
+    }
+
+    /** @deprecated Use {@link #setCustomTimeOffsetDateTime(OffsetDateTime)} */
+    @Override
+    @Deprecated
     public Builder setCustomTime(Long customTime) {
       infoBuilder.setCustomTime(customTime);
       return this;
     }
 
+    @Override
+    public BlobInfo.Builder setCustomTimeOffsetDateTime(OffsetDateTime customTime) {
+      infoBuilder.setCustomTimeOffsetDateTime(customTime);
+      return this;
+    }
+
     @Override
     Builder setIsDirectory(boolean isDirectory) {
       infoBuilder.setIsDirectory(isDirectory);
@@ -450,12 +491,21 @@ public class Blob extends BlobInfo {
       return this;
     }
 
+    /** @deprecated Use {@link #setRetentionExpirationTimeOffsetDateTime(OffsetDateTime)} */
     @Override
+    @Deprecated
     Builder setRetentionExpirationTime(Long retentionExpirationTime) {
       infoBuilder.setRetentionExpirationTime(retentionExpirationTime);
       return this;
     }
 
+    @Override
+    BlobInfo.Builder setRetentionExpirationTimeOffsetDateTime(
+        OffsetDateTime retentionExpirationTime) {
+      infoBuilder.setRetentionExpirationTimeOffsetDateTime(retentionExpirationTime);
+      return this;
+    }
+
     @Override
     public Blob build() {
       return new Blob(storage, infoBuilder);
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java
index c1db1dfa..73575546 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java
@@ -263,7 +263,7 @@ public class BlobInfo implements Serializable {
      * BlobInfo blob = BlobInfo.newBuilder(bucketName, blobName).setCustomTime(customTime).build();
      * }</pre>
      *
-     * @deprecated {@link #setCustomTime(OffsetDateTime)}
+     * @deprecated Use {@link #setCustomTimeOffsetDateTime(OffsetDateTime)}
      */
     @Deprecated
     public Builder setCustomTime(Long customTime) {
@@ -286,7 +286,7 @@ public class BlobInfo implements Serializable {
      * BlobInfo blob = BlobInfo.newBuilder(bucketName, blobName).setCustomTime(customTime).build();
      * }</pre>
      */
-    public Builder setCustomTime(OffsetDateTime customTime) {
+    public Builder setCustomTimeOffsetDateTime(OffsetDateTime customTime) {
       // provide an implementation for source and binary compatibility which we override ourselves
       return setCustomTime(millisOffsetDateTimeCodec.decode(customTime));
     }
@@ -311,7 +311,7 @@ public class BlobInfo implements Serializable {
      * Sets the modification time of an object's storage class. Once set it can't be unset directly,
      * the only way is to rewrite the object with the desired storage class.
      *
-     * @deprecated {@link #setTimeStorageClassUpdated(OffsetDateTime)}
+     * @deprecated Use {@link #setTimeStorageClassUpdatedOffsetDateTime(OffsetDateTime)}
      */
     @Deprecated
     public Builder setTimeStorageClassUpdated(Long timeStorageClassUpdated) {
@@ -320,7 +320,8 @@ public class BlobInfo implements Serializable {
               + " or use com.google.cloud.storage.Blob.");
     }
 
-    public Builder setTimeStorageClassUpdated(OffsetDateTime timeStorageClassUpdated) {
+    public Builder setTimeStorageClassUpdatedOffsetDateTime(
+        OffsetDateTime timeStorageClassUpdated) {
       // provide an implementation for source and binary compatibility which we override ourselves
       return setTimeStorageClassUpdated(millisOffsetDateTimeCodec.decode(timeStorageClassUpdated));
     }
@@ -330,29 +331,29 @@ public class BlobInfo implements Serializable {
 
     abstract Builder setMetageneration(Long metageneration);
 
-    /** @deprecated {@link #setDeleteTime(OffsetDateTime)} */
+    /** @deprecated Use {@link #setDeleteTimeOffsetDateTime(OffsetDateTime)} */
     @Deprecated
     abstract Builder setDeleteTime(Long deleteTime);
 
-    Builder setDeleteTime(OffsetDateTime deleteTime) {
+    Builder setDeleteTimeOffsetDateTime(OffsetDateTime deleteTime) {
       // provide an implementation for source and binary compatibility which we override ourselves
       return setDeleteTime(millisOffsetDateTimeCodec.decode(deleteTime));
     }
 
-    /** @deprecated {@link #setUpdateTime(OffsetDateTime)} */
+    /** @deprecated Use {@link #setUpdateTimeOffsetDateTime(OffsetDateTime)} */
     @Deprecated
     abstract Builder setUpdateTime(Long updateTime);
 
-    Builder setUpdateTime(OffsetDateTime updateTime) {
+    Builder setUpdateTimeOffsetDateTime(OffsetDateTime updateTime) {
       // provide an implementation for source and binary compatibility which we override ourselves
       return setUpdateTime(millisOffsetDateTimeCodec.decode(updateTime));
     }
 
-    /** @deprecated {@link #setCreateTime(OffsetDateTime)} */
+    /** @deprecated Use {@link #setCreateTimeOffsetDateTime(OffsetDateTime)} */
     @Deprecated
     abstract Builder setCreateTime(Long createTime);
 
-    Builder setCreateTime(OffsetDateTime createTime) {
+    Builder setCreateTimeOffsetDateTime(OffsetDateTime createTime) {
       // provide an implementation for source and binary compatibility which we override ourselves
       return setCreateTime(millisOffsetDateTimeCodec.decode(createTime));
     }
@@ -388,13 +389,13 @@ public class BlobInfo implements Serializable {
     @BetaApi
     public abstract Builder setTemporaryHold(Boolean temporaryHold);
 
-    /** @deprecated {@link #setRetentionExpirationTime(OffsetDateTime)} */
+    /** @deprecated {@link #setRetentionExpirationTimeOffsetDateTime(OffsetDateTime)} */
     @BetaApi
     @Deprecated
     abstract Builder setRetentionExpirationTime(Long retentionExpirationTime);
 
     @BetaApi
-    Builder setRetentionExpirationTime(OffsetDateTime retentionExpirationTime) {
+    Builder setRetentionExpirationTimeOffsetDateTime(OffsetDateTime retentionExpirationTime) {
       // provide an implementation for source and binary compatibility which we override ourselves
       return setRetentionExpirationTime(millisOffsetDateTimeCodec.decode(retentionExpirationTime));
     }
@@ -587,13 +588,15 @@ public class BlobInfo implements Serializable {
       return this;
     }
 
+    /** @deprecated {@link #setCustomTimeOffsetDateTime(OffsetDateTime)} */
     @Override
+    @Deprecated
     public Builder setCustomTime(Long customTime) {
-      return setCustomTime(millisOffsetDateTimeCodec.encode(customTime));
+      return setCustomTimeOffsetDateTime(millisOffsetDateTimeCodec.encode(customTime));
     }
 
     @Override
-    public Builder setCustomTime(OffsetDateTime customTime) {
+    public Builder setCustomTimeOffsetDateTime(OffsetDateTime customTime) {
       this.customTime = customTime;
       return this;
     }
@@ -645,13 +648,17 @@ public class BlobInfo implements Serializable {
       return this;
     }
 
+    /** @deprecated Use {@link #setTimeStorageClassUpdatedOffsetDateTime(OffsetDateTime)} */
+    @Deprecated
     @Override
     public Builder setTimeStorageClassUpdated(Long timeStorageClassUpdated) {
-      return setTimeStorageClassUpdated(millisOffsetDateTimeCodec.encode(timeStorageClassUpdated));
+      return setTimeStorageClassUpdatedOffsetDateTime(
+          millisOffsetDateTimeCodec.encode(timeStorageClassUpdated));
     }
 
     @Override
-    public Builder setTimeStorageClassUpdated(OffsetDateTime timeStorageClassUpdated) {
+    public Builder setTimeStorageClassUpdatedOffsetDateTime(
+        OffsetDateTime timeStorageClassUpdated) {
       this.timeStorageClassUpdated = timeStorageClassUpdated;
       return this;
     }
@@ -662,35 +669,40 @@ public class BlobInfo implements Serializable {
       return this;
     }
 
+    /** @deprecated Use {@link #setDeleteTimeOffsetDateTime(OffsetDateTime)} */
+    @Deprecated
     @Override
     Builder setDeleteTime(Long deleteTime) {
-      return setDeleteTime(millisOffsetDateTimeCodec.encode(deleteTime));
+      return setDeleteTimeOffsetDateTime(millisOffsetDateTimeCodec.encode(deleteTime));
     }
 
     @Override
-    Builder setDeleteTime(OffsetDateTime deleteTime) {
+    Builder setDeleteTimeOffsetDateTime(OffsetDateTime deleteTime) {
       this.deleteTime = deleteTime;
       return this;
     }
 
+    /** @deprecated Use {@link #setUpdateTimeOffsetDateTime(OffsetDateTime)} */
     @Override
     Builder setUpdateTime(Long updateTime) {
-      return setUpdateTime(millisOffsetDateTimeCodec.encode(updateTime));
+      return setUpdateTimeOffsetDateTime(millisOffsetDateTimeCodec.encode(updateTime));
     }
 
     @Override
-    Builder setUpdateTime(OffsetDateTime updateTime) {
+    Builder setUpdateTimeOffsetDateTime(OffsetDateTime updateTime) {
       this.updateTime = updateTime;
       return this;
     }
 
+    /** @deprecated Use {@link #setCreateTimeOffsetDateTime(OffsetDateTime)} */
+    @Deprecated
     @Override
     Builder setCreateTime(Long createTime) {
-      return setCreateTime(millisOffsetDateTimeCodec.encode(createTime));
+      return setCreateTimeOffsetDateTime(millisOffsetDateTimeCodec.encode(createTime));
     }
 
     @Override
-    Builder setCreateTime(OffsetDateTime createTime) {
+    Builder setCreateTimeOffsetDateTime(OffsetDateTime createTime) {
       this.createTime = createTime;
       return this;
     }
@@ -725,13 +737,16 @@ public class BlobInfo implements Serializable {
       return this;
     }
 
+    /** @deprecated {@link #setRetentionExpirationTimeOffsetDateTime(OffsetDateTime)} */
     @Override
+    @Deprecated
     Builder setRetentionExpirationTime(Long retentionExpirationTime) {
-      return setRetentionExpirationTime(millisOffsetDateTimeCodec.encode(retentionExpirationTime));
+      return setRetentionExpirationTimeOffsetDateTime(
+          millisOffsetDateTimeCodec.encode(retentionExpirationTime));
     }
 
     @Override
-    Builder setRetentionExpirationTime(OffsetDateTime retentionExpirationTime) {
+    Builder setRetentionExpirationTimeOffsetDateTime(OffsetDateTime retentionExpirationTime) {
       this.retentionExpirationTime = retentionExpirationTime;
       return this;
     }
@@ -980,7 +995,7 @@ public class BlobInfo implements Serializable {
    * Returns the deletion time of the blob expressed as the number of milliseconds since the Unix
    * epoch.
    *
-   * @deprecated {@link #getDeleteTimeOffsetDateTime()}
+   * @deprecated Use {@link #getDeleteTimeOffsetDateTime()}
    */
   @Deprecated
   public Long getDeleteTime() {
@@ -996,7 +1011,7 @@ public class BlobInfo implements Serializable {
    * Returns the last modification time of the blob's metadata expressed as the number of
    * milliseconds since the Unix epoch.
    *
-   * @deprecated {@link #getUpdateTimeOffsetDateTime()}
+   * @deprecated Use {@link #getUpdateTimeOffsetDateTime()}
    */
   @Deprecated
   public Long getUpdateTime() {
@@ -1012,7 +1027,7 @@ public class BlobInfo implements Serializable {
    * Returns the creation time of the blob expressed as the number of milliseconds since the Unix
    * epoch.
    *
-   * @deprecated {@link #getCreateTimeOffsetDateTime()}
+   * @deprecated Use {@link #getCreateTimeOffsetDateTime()}
    */
   @Deprecated
   public Long getCreateTime() {
@@ -1027,7 +1042,7 @@ public class BlobInfo implements Serializable {
   /**
    * Returns the custom time specified by the user for an object.
    *
-   * @deprecated {@link #getCustomTimeOffsetDateTime()}
+   * @deprecated Use {@link #getCustomTimeOffsetDateTime()}
    */
   @Deprecated
   public Long getCustomTime() {
@@ -1068,7 +1083,7 @@ public class BlobInfo implements Serializable {
    * Returns the time that the object's storage class was last changed or the time of the object
    * creation.
    *
-   * @deprecated {@link #getTimeStorageClassUpdatedOffsetDateTime()}
+   * @deprecated Use {@link #getTimeStorageClassUpdatedOffsetDateTime()}
    */
   @Deprecated
   public Long getTimeStorageClassUpdated() {
@@ -1146,7 +1161,7 @@ public class BlobInfo implements Serializable {
    * Returns the retention expiration time of the blob as {@code Long}, if a retention period is
    * defined. If retention period is not defined this value returns {@code null}
    *
-   * @deprecated {@link #getRetentionExpirationTimeOffsetDateTime()}
+   * @deprecated Use {@link #getRetentionExpirationTimeOffsetDateTime()}
    */
   @BetaApi
   @Deprecated
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java
index a2259276..2bb3f4be 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java
@@ -38,6 +38,8 @@ import java.io.InputStream;
 import java.io.ObjectInputStream;
 import java.io.Serializable;
 import java.security.Key;
+import java.time.Duration;
+import java.time.OffsetDateTime;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -558,6 +560,11 @@ public class Bucket extends BucketInfo {
       return this;
     }
 
+    /**
+     * @deprecated Use {@link #setLifecycleRules(Iterable)} instead, as in {@code
+     *     setLifecycleRules(Collections.singletonList( new BucketInfo.LifecycleRule(
+     *     LifecycleAction.newDeleteAction(), LifecycleCondition.newBuilder().setAge(5).build())));}
+     */
     @Override
     @Deprecated
     public Builder setDeleteRules(Iterable<? extends DeleteRule> rules) {
@@ -601,18 +608,34 @@ public class Bucket extends BucketInfo {
       return this;
     }
 
+    /** @deprecated Use {@link #setCreateTimeOffsetDateTime(OffsetDateTime)} */
     @Override
+    @Deprecated
     Builder setCreateTime(Long createTime) {
       infoBuilder.setCreateTime(createTime);
       return this;
     }
 
     @Override
+    BucketInfo.Builder setCreateTimeOffsetDateTime(OffsetDateTime createTime) {
+      infoBuilder.setCreateTimeOffsetDateTime(createTime);
+      return this;
+    }
+
+    /** @deprecated Use {@link #setUpdateTimeOffsetDateTime(OffsetDateTime)} */
+    @Override
+    @Deprecated
     Builder setUpdateTime(Long updateTime) {
       infoBuilder.setUpdateTime(updateTime);
       return this;
     }
 
+    @Override
+    BucketInfo.Builder setUpdateTimeOffsetDateTime(OffsetDateTime updateTime) {
+      infoBuilder.setUpdateTimeOffsetDateTime(updateTime);
+      return this;
+    }
+
     @Override
     Builder setMetageneration(Long metageneration) {
       infoBuilder.setMetageneration(metageneration);
@@ -655,24 +678,41 @@ public class Bucket extends BucketInfo {
       return this;
     }
 
+    /** @deprecated {@link #setRetentionEffectiveTimeOffsetDateTime(OffsetDateTime)} */
     @Override
+    @Deprecated
     Builder setRetentionEffectiveTime(Long retentionEffectiveTime) {
       infoBuilder.setRetentionEffectiveTime(retentionEffectiveTime);
       return this;
     }
 
+    @Override
+    BucketInfo.Builder setRetentionEffectiveTimeOffsetDateTime(
+        OffsetDateTime retentionEffectiveTime) {
+      infoBuilder.setRetentionEffectiveTimeOffsetDateTime(retentionEffectiveTime);
+      return this;
+    }
+
     @Override
     Builder setRetentionPolicyIsLocked(Boolean retentionIsLocked) {
       infoBuilder.setRetentionPolicyIsLocked(retentionIsLocked);
       return this;
     }
 
+    /** @deprecated Use {@link #setRetentionPeriodDuration(Duration)} */
     @Override
+    @Deprecated
     public Builder setRetentionPeriod(Long retentionPeriod) {
       infoBuilder.setRetentionPeriod(retentionPeriod);
       return this;
     }
 
+    @Override
+    public BucketInfo.Builder setRetentionPeriodDuration(Duration retentionPeriod) {
+      infoBuilder.setRetentionPeriodDuration(retentionPeriod);
+      return this;
+    }
+
     @Override
     public Builder setIamConfiguration(IamConfiguration iamConfiguration) {
       infoBuilder.setIamConfiguration(iamConfiguration);
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
index 806cb91b..fb30b7d3 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
@@ -17,6 +17,8 @@
 package com.google.cloud.storage;
 
 import static com.google.cloud.storage.BackwardCompatibilityUtils.millisOffsetDateTimeCodec;
+import static com.google.cloud.storage.BackwardCompatibilityUtils.millisUtcCodec;
+import static com.google.cloud.storage.BackwardCompatibilityUtils.nullableDurationMillisCodec;
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -34,6 +36,7 @@ import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
+import java.time.Duration;
 import java.time.OffsetDateTime;
 import java.util.List;
 import java.util.Map;
@@ -81,7 +84,7 @@ public class BucketInfo implements Serializable {
   private final Boolean defaultEventBasedHold;
   private final OffsetDateTime retentionEffectiveTime;
   private final Boolean retentionPolicyIsLocked;
-  private final Long retentionPeriod; // TODO: duration
+  private final Duration retentionPeriod;
   private final IamConfiguration iamConfiguration;
   private final String locationType;
   private final Logging logging;
@@ -262,11 +265,11 @@ public class BucketInfo implements Serializable {
        * this field should never be set by a user--it's automatically set by the backend when {@code
        * enabled} is set to true.
        *
-       * @deprecated {@link #setUniformBucketLevelAccessLockedTime(OffsetDateTime)}
+       * @deprecated {@link #setUniformBucketLevelAccessLockedTimeOffsetDateTime(OffsetDateTime)}
        */
       @Deprecated
       Builder setUniformBucketLevelAccessLockedTime(Long uniformBucketLevelAccessLockedTime) {
-        return setUniformBucketLevelAccessLockedTime(
+        return setUniformBucketLevelAccessLockedTimeOffsetDateTime(
             millisOffsetDateTimeCodec.encode(uniformBucketLevelAccessLockedTime));
       }
 
@@ -276,7 +279,7 @@ public class BucketInfo implements Serializable {
        * this field should never be set by a user--it's automatically set by the backend when {@code
        * enabled} is set to true.
        */
-      Builder setUniformBucketLevelAccessLockedTime(
+      Builder setUniformBucketLevelAccessLockedTimeOffsetDateTime(
           OffsetDateTime uniformBucketLevelAccessLockedTime) {
         this.uniformBucketLevelAccessLockedTime = uniformBucketLevelAccessLockedTime;
         return this;
@@ -812,8 +815,6 @@ public class BucketInfo implements Serializable {
           Conversions.apiary().deleteRule().encode(this),
           Conversions.apiary().deleteRule().encode(other));
     }
-
-    abstract void populateCondition(Rule.Condition condition);
   }
 
   /**
@@ -847,15 +848,10 @@ public class BucketInfo implements Serializable {
     public int getDaysToLive() {
       return daysToLive;
     }
-
-    @Override
-    void populateCondition(Rule.Condition condition) {
-      condition.setAge(daysToLive);
-    }
   }
 
   static class RawDeleteRule extends DeleteRule {
-
+    // TODO(benwhitehead): investigate having deleted the populateCondition method
     private static final long serialVersionUID = -7166938278642301933L;
 
     private transient Rule rule;
@@ -865,15 +861,6 @@ public class BucketInfo implements Serializable {
       this.rule = rule;
     }
 
-    @Override
-    void populateCondition(Rule.Condition condition) {
-      log.warning(
-          "The lifecycle condition "
-              + condition
-              + " is not currently supported. Please update to the latest version of google-cloud-java."
-              + " Also, use LifecycleRule rather than the deprecated DeleteRule.");
-    }
-
     private void writeObject(ObjectOutputStream out) throws IOException {
       out.defaultWriteObject();
       out.writeUTF(rule.toString());
@@ -900,26 +887,39 @@ public class BucketInfo implements Serializable {
   public static class CreatedBeforeDeleteRule extends DeleteRule {
 
     private static final long serialVersionUID = 881692650279195867L;
-    private final long timeMillis;
+    private final OffsetDateTime time;
 
     /**
      * Creates an {@code CreatedBeforeDeleteRule} object.
      *
      * @param timeMillis a date in UTC. Blobs that have been created before midnight of the provided
      *     date meet the delete condition
+     * @deprecated Use {@link #CreatedBeforeDeleteRule(OffsetDateTime)} instead
      */
+    @Deprecated
     public CreatedBeforeDeleteRule(long timeMillis) {
+      this(millisUtcCodec.encode(timeMillis));
+    }
+
+    /**
+     * Creates an {@code CreatedBeforeDeleteRule} object.
+     *
+     * @param time Blobs that have been created before midnight of the provided date meet the delete
+     *     condition
+     */
+    public CreatedBeforeDeleteRule(OffsetDateTime time) {
       super(Type.CREATE_BEFORE);
-      this.timeMillis = timeMillis;
+      this.time = time;
     }
 
+    /** @deprecated {@link #getTime()} */
+    @Deprecated
     public long getTimeMillis() {
-      return timeMillis;
+      return millisUtcCodec.decode(time);
     }
 
-    @Override
-    void populateCondition(Rule.Condition condition) {
-      condition.setCreatedBefore(new DateTime(true, timeMillis, 0));
+    public OffsetDateTime getTime() {
+      return time;
     }
   }
 
@@ -951,11 +951,6 @@ public class BucketInfo implements Serializable {
     public int getNumNewerVersions() {
       return numNewerVersions;
     }
-
-    @Override
-    void populateCondition(Rule.Condition condition) {
-      condition.setNumNewerVersions(numNewerVersions);
-    }
   }
 
   /**
@@ -985,11 +980,6 @@ public class BucketInfo implements Serializable {
     public boolean isLive() {
       return isLive;
     }
-
-    @Override
-    void populateCondition(Rule.Condition condition) {
-      condition.setIsLive(isLive);
-    }
   }
 
   /** Builder for {@code BucketInfo}. */
@@ -1029,7 +1019,7 @@ public class BucketInfo implements Serializable {
     /**
      * Sets the bucket's lifecycle configuration as a number of delete rules.
      *
-     * @deprecated Use {@code setLifecycleRules} instead, as in {@code
+     * @deprecated Use {@link #setLifecycleRules(Iterable)} instead, as in {@code
      *     setLifecycleRules(Collections.singletonList( new BucketInfo.LifecycleRule(
      *     LifecycleAction.newDeleteAction(), LifecycleCondition.newBuilder().setAge(5).build())));}
      */
@@ -1073,21 +1063,21 @@ public class BucketInfo implements Serializable {
 
     abstract Builder setEtag(String etag);
 
-    /** @deprecated {@link #setCreateTime(OffsetDateTime)} */
+    /** @deprecated {@link #setCreateTimeOffsetDateTime(OffsetDateTime)} */
     @Deprecated
     abstract Builder setCreateTime(Long createTime);
 
-    Builder setCreateTime(OffsetDateTime createTime) {
+    Builder setCreateTimeOffsetDateTime(OffsetDateTime createTime) {
       // provide an implementation for source and binary compatibility which we override ourselves
       setCreateTime(millisOffsetDateTimeCodec.decode(createTime));
       return this;
     }
 
-    /** @deprecated {@link #setUpdateTime(OffsetDateTime)} */
+    /** @deprecated {@link #setUpdateTimeOffsetDateTime(OffsetDateTime)} */
     @Deprecated
     abstract Builder setUpdateTime(Long updateTime);
 
-    Builder setUpdateTime(OffsetDateTime updateTime) {
+    Builder setUpdateTimeOffsetDateTime(OffsetDateTime updateTime) {
       // provide an implementation for source and binary compatibility which we override ourselves
       setCreateTime(millisOffsetDateTimeCodec.decode(updateTime));
       return this;
@@ -1134,13 +1124,13 @@ public class BucketInfo implements Serializable {
     @BetaApi
     public abstract Builder setDefaultEventBasedHold(Boolean defaultEventBasedHold);
 
-    /** @deprecated {@link #setRetentionEffectiveTime(OffsetDateTime)} */
+    /** @deprecated {@link #setRetentionEffectiveTimeOffsetDateTime(OffsetDateTime)} */
     @BetaApi
     @Deprecated
     abstract Builder setRetentionEffectiveTime(Long retentionEffectiveTime);
 
     @BetaApi
-    Builder setRetentionEffectiveTime(OffsetDateTime retentionEffectiveTime) {
+    Builder setRetentionEffectiveTimeOffsetDateTime(OffsetDateTime retentionEffectiveTime) {
       return setRetentionEffectiveTime(millisOffsetDateTimeCodec.decode(retentionEffectiveTime));
     }
 
@@ -1150,10 +1140,22 @@ public class BucketInfo implements Serializable {
     /**
      * If policy is not locked this value can be cleared, increased, and decreased. If policy is
      * locked the retention period can only be increased.
+     *
+     * @deprecated Use {@link #setRetentionPeriodDuration(Duration)}
      */
     @BetaApi
+    @Deprecated
     public abstract Builder setRetentionPeriod(Long retentionPeriod);
 
+    /**
+     * If policy is not locked this value can be cleared, increased, and decreased. If policy is
+     * locked the retention period can only be increased.
+     */
+    @BetaApi
+    public Builder setRetentionPeriodDuration(Duration retentionPeriod) {
+      return setRetentionPeriod(nullableDurationMillisCodec.encode(retentionPeriod));
+    }
+
     /**
      * Sets the IamConfiguration to specify whether IAM access should be enabled.
      *
@@ -1196,7 +1198,7 @@ public class BucketInfo implements Serializable {
     private Boolean defaultEventBasedHold;
     private OffsetDateTime retentionEffectiveTime;
     private Boolean retentionPolicyIsLocked;
-    private Long retentionPeriod;
+    private Duration retentionPeriod;
     private IamConfiguration iamConfiguration;
     private String locationType;
     private Logging logging;
@@ -1331,7 +1333,7 @@ public class BucketInfo implements Serializable {
       return this;
     }
 
-    /** @deprecated {@link #setCreateTime(OffsetDateTime)} */
+    /** @deprecated {@link #setCreateTimeOffsetDateTime(OffsetDateTime)} */
     @Deprecated
     @Override
     Builder setCreateTime(Long createTime) {
@@ -1340,12 +1342,12 @@ public class BucketInfo implements Serializable {
     }
 
     @Override
-    Builder setCreateTime(OffsetDateTime createTime) {
+    Builder setCreateTimeOffsetDateTime(OffsetDateTime createTime) {
       this.createTime = createTime;
       return this;
     }
 
-    /** @deprecated {@link #setUpdateTime(OffsetDateTime)} */
+    /** @deprecated {@link #setUpdateTimeOffsetDateTime(OffsetDateTime)} */
     @Deprecated
     @Override
     Builder setUpdateTime(Long updateTime) {
@@ -1354,7 +1356,7 @@ public class BucketInfo implements Serializable {
     }
 
     @Override
-    Builder setUpdateTime(OffsetDateTime updateTime) {
+    Builder setUpdateTimeOffsetDateTime(OffsetDateTime updateTime) {
       this.updateTime = updateTime;
       return this;
     }
@@ -1414,14 +1416,16 @@ public class BucketInfo implements Serializable {
       return this;
     }
 
+    /** @deprecated Use {@link #setRetentionEffectiveTimeOffsetDateTime(OffsetDateTime)} */
     @Override
     @Deprecated
     Builder setRetentionEffectiveTime(Long retentionEffectiveTime) {
-      return setRetentionEffectiveTime(millisOffsetDateTimeCodec.encode(retentionEffectiveTime));
+      return setRetentionEffectiveTimeOffsetDateTime(
+          millisOffsetDateTimeCodec.encode(retentionEffectiveTime));
     }
 
     @Override
-    Builder setRetentionEffectiveTime(OffsetDateTime retentionEffectiveTime) {
+    Builder setRetentionEffectiveTimeOffsetDateTime(OffsetDateTime retentionEffectiveTime) {
       this.retentionEffectiveTime = retentionEffectiveTime;
       return this;
     }
@@ -1433,9 +1437,15 @@ public class BucketInfo implements Serializable {
       return this;
     }
 
+    /** @deprecated Use {@link #setRetentionPeriodDuration(Duration)} */
     @Override
     public Builder setRetentionPeriod(Long retentionPeriod) {
-      this.retentionPeriod = firstNonNull(retentionPeriod, Data.<Long>nullOf(Long.class));
+      return setRetentionPeriodDuration(nullableDurationMillisCodec.decode(retentionPeriod));
+    }
+
+    @Override
+    public Builder setRetentionPeriodDuration(Duration retentionPeriod) {
+      this.retentionPeriod = retentionPeriod;
       return this;
     }
 
@@ -1742,6 +1752,8 @@ public class BucketInfo implements Serializable {
   /**
    * Returns the retention effective time a policy took effect if a retention policy is defined as a
    * {@code Long}.
+   *
+   * @deprecated Use {@link #getRetentionPeriodDuration()}
    */
   @BetaApi
   @Deprecated
@@ -1778,9 +1790,20 @@ public class BucketInfo implements Serializable {
     return Data.isNull(retentionPolicyIsLocked) ? null : retentionPolicyIsLocked;
   }
 
-  /** Returns the retention policy retention period. */
+  /**
+   * Returns the retention policy retention period.
+   *
+   * @deprecated Use {@link #getRetentionPeriodDuration()}
+   */
   @BetaApi
+  @Deprecated
   public Long getRetentionPeriod() {
+    return nullableDurationMillisCodec.encode(retentionPeriod);
+  }
+
+  /** Returns the retention policy retention period. */
+  @BetaApi
+  public Duration getRetentionPeriodDuration() {
     return retentionPeriod;
   }
 
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java
index 51b9cd33..6b679c25 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java
@@ -19,20 +19,26 @@ package com.google.cloud.storage;
 import static com.google.cloud.storage.Utils.ifNonNull;
 import static com.google.cloud.storage.Utils.todo;
 
+import com.google.cloud.storage.BlobInfo.CustomerEncryption;
 import com.google.cloud.storage.Conversions.Codec;
+import com.google.common.io.BaseEncoding;
+import com.google.common.primitives.Ints;
+import com.google.protobuf.ByteString;
 import com.google.protobuf.Timestamp;
 import com.google.protobuf.util.Timestamps;
 import com.google.storage.v2.Bucket;
 import com.google.storage.v2.Bucket.Billing;
 import com.google.storage.v2.HmacKeyMetadata;
 import com.google.storage.v2.Object;
+import com.google.storage.v2.ObjectAccessControl;
+import com.google.storage.v2.ObjectChecksums;
 import java.util.concurrent.TimeUnit;
 
 final class GrpcConversions {
   static final GrpcConversions INSTANCE = new GrpcConversions();
 
   private final Codec<?, ?> entityCodec = Codec.of(Utils::todo, Utils::todo);
-  private final Codec<?, ?> objectAclCodec = Codec.of(Utils::todo, Utils::todo);
+  private final Codec<Acl, ObjectAccessControl> objectAclCodec = Codec.of(Utils::todo, Utils::todo);
   private final Codec<?, ?> bucketAclCodec = Codec.of(Utils::todo, Utils::todo);
   private final Codec<HmacKey.HmacKeyMetadata, HmacKeyMetadata> hmacKeyMetadataCodec =
       Codec.of(this::hmacKeyMetadataEncode, this::hmacKeyMetadataDecode);
@@ -46,10 +52,16 @@ final class GrpcConversions {
   private final Codec<?, ?> deleteRuleCodec = Codec.of(Utils::todo, Utils::todo);
   private final Codec<BucketInfo, Bucket> bucketInfoCodec =
       Codec.of(Utils::todo, this::bucketInfoDecode);
-  private final Codec<?, ?> customerEncryptionCodec = Codec.of(Utils::todo, Utils::todo);
-  private final Codec<BlobId, Object> blobIdCodec = Codec.of(Utils::todo, Utils::todo);
-  private final Codec<?, ?> blobInfoCodec = Codec.of(Utils::todo, Utils::todo);
+  private final Codec<CustomerEncryption, com.google.storage.v2.CustomerEncryption>
+      customerEncryptionCodec =
+          Codec.of(this::customerEncryptionEncode, this::customerEncryptionDecode);
+  private final Codec<BlobId, Object> blobIdCodec =
+      Codec.of(this::blobIdEncode, this::blobIdDecode);
+  private final Codec<BlobInfo, Object> blobInfoCodec =
+      Codec.of(this::blobInfoEncode, this::blobInfoDecode);
   private final Codec<?, ?> notificationInfoCodec = Codec.of(Utils::todo, Utils::todo);
+  private final Codec<Integer, String> crc32cCodec =
+      Codec.of(this::crc32cEncode, this::crc32cDecode);
 
   private GrpcConversions() {}
 
@@ -57,8 +69,8 @@ final class GrpcConversions {
     return todo();
   }
 
-  Codec<?, ?> objectAcl() {
-    return todo();
+  Codec<Acl, ObjectAccessControl> objectAcl() {
+    return objectAclCodec;
   }
 
   Codec<?, ?> bucketAcl() {
@@ -101,16 +113,16 @@ final class GrpcConversions {
     return bucketInfoCodec;
   }
 
-  Codec<?, ?> customerEncryption() {
-    return todo();
+  Codec<CustomerEncryption, com.google.storage.v2.CustomerEncryption> customerEncryption() {
+    return customerEncryptionCodec;
   }
 
   Codec<BlobId, Object> blobId() {
     return blobIdCodec;
   }
 
-  Codec<?, ?> blobInfo() {
-    return todo();
+  Codec<BlobInfo, Object> blobInfo() {
+    return blobInfoCodec;
   }
 
   Codec<?, ?> notificationInfo() {
@@ -192,4 +204,134 @@ final class GrpcConversions {
   private ServiceAccount serviceAccountDecode(com.google.storage.v2.ServiceAccount from) {
     return ServiceAccount.of(from.getEmailAddress());
   }
+
+  private com.google.storage.v2.CustomerEncryption customerEncryptionEncode(
+      CustomerEncryption from) {
+    return com.google.storage.v2.CustomerEncryption.newBuilder()
+        .setEncryptionAlgorithm(from.getEncryptionAlgorithm())
+        .setKeySha256Bytes(ByteString.copyFrom(BaseEncoding.base64().decode(from.getKeySha256())))
+        .build();
+  }
+
+  private CustomerEncryption customerEncryptionDecode(
+      com.google.storage.v2.CustomerEncryption from) {
+    return new CustomerEncryption(
+        from.getEncryptionAlgorithm(),
+        BaseEncoding.base64().encode(from.getKeySha256Bytes().toByteArray()));
+  }
+
+  private Object blobIdEncode(BlobId from) {
+    return Object.newBuilder()
+        .setName(from.getName())
+        .setBucket(from.getBucket())
+        .setGeneration(from.getGeneration())
+        .build();
+  }
+
+  private BlobId blobIdDecode(Object from) {
+    return BlobId.of(from.getBucket(), from.getName(), from.getGeneration());
+  }
+
+  private Object blobInfoEncode(BlobInfo from) {
+    Object.Builder toBuilder =
+        Object.newBuilder()
+            .setBucket(from.getBucket())
+            .setName(from.getName())
+            .setGeneration(from.getGeneration());
+    ifNonNull(from.getCacheControl(), toBuilder::setCacheControl);
+    ifNonNull(from.getSize(), toBuilder::setSize);
+    ifNonNull(from.getContentType(), toBuilder::setContentType);
+    ifNonNull(from.getContentEncoding(), toBuilder::setContentEncoding);
+    ifNonNull(from.getContentDisposition(), toBuilder::setContentDisposition);
+    ifNonNull(from.getContentLanguage(), toBuilder::setContentLanguage);
+    ifNonNull(from.getComponentCount(), toBuilder::setComponentCount);
+    if (from.getMd5() != null || from.getCrc32c() != null) {
+      ObjectChecksums.Builder objectChecksums = ObjectChecksums.newBuilder();
+      if (from.getMd5() != null) {
+        objectChecksums.setMd5Hash(
+            ByteString.copyFrom(BaseEncoding.base64().decode(from.getMd5())));
+      }
+      if (from.getCrc32c() != null) {
+        objectChecksums.setCrc32C(crc32cCodec.decode(from.getCrc32c()));
+      }
+      toBuilder.setChecksums(objectChecksums.build());
+    }
+    toBuilder.setMetageneration(from.getMetageneration());
+    ifNonNull(from.getDeleteTime(), Timestamps::fromMillis, toBuilder::setDeleteTime);
+    ifNonNull(from.getUpdateTime(), Timestamps::fromMillis, toBuilder::setUpdateTime);
+    ifNonNull(from.getCreateTime(), Timestamps::fromMillis, toBuilder::setCreateTime);
+    ifNonNull(from.getCustomTime(), Timestamps::fromMillis, toBuilder::setCustomTime);
+    ifNonNull(
+        from.getCustomerEncryption(),
+        customerEncryptionCodec::encode,
+        toBuilder::setCustomerEncryption);
+    ifNonNull(from.getStorageClass(), StorageClass::toString, toBuilder::setStorageClass);
+    ifNonNull(
+        from.getTimeStorageClassUpdated(),
+        Timestamps::fromMillis,
+        toBuilder::setUpdateStorageClassTime);
+    ifNonNull(from.getKmsKeyName(), toBuilder::setKmsKey);
+    ifNonNull(from.getEventBasedHold(), toBuilder::setEventBasedHold);
+    ifNonNull(from.getTemporaryHold(), toBuilder::setTemporaryHold);
+    ifNonNull(
+        from.getRetentionExpirationTime(),
+        Timestamps::fromMillis,
+        toBuilder::setRetentionExpireTime);
+    // TODO(sydmunro): Add Selflink when available
+    // TODO(sydmunro): Add etag when available
+    // TODO(sydmunro): Add Owner
+    // TODO(sydmunro): Add user metadata
+    // TODO(sydmunro): Object ACL
+    return toBuilder.build();
+  }
+
+  private BlobInfo blobInfoDecode(Object from) {
+    BlobInfo.Builder toBuilder =
+        BlobInfo.newBuilder(BlobId.of(from.getBucket(), from.getName(), from.getGeneration()));
+    ifNonNull(from.getCacheControl(), toBuilder::setCacheControl);
+    ifNonNull(from.getSize(), toBuilder::setSize);
+    ifNonNull(from.getContentType(), toBuilder::setContentType);
+    ifNonNull(from.getContentEncoding(), toBuilder::setContentEncoding);
+    ifNonNull(from.getContentDisposition(), toBuilder::setContentDisposition);
+    ifNonNull(from.getContentLanguage(), toBuilder::setContentLanguage);
+    ifNonNull(from.getComponentCount(), toBuilder::setComponentCount);
+    if (from.getChecksums() != null) {
+      if (from.getChecksums().hasCrc32C()) {
+        toBuilder.setCrc32c(crc32cCodec.encode(from.getChecksums().getCrc32C()));
+      }
+      if (from.getChecksums().getMd5Hash() != null) {
+        toBuilder.setMd5(
+            BaseEncoding.base64().encode(from.getChecksums().getMd5Hash().toByteArray()));
+      }
+    }
+    ifNonNull(from.getMetageneration(), toBuilder::setMetageneration);
+    ifNonNull(from.getDeleteTime(), Timestamps::toMillis, toBuilder::setDeleteTime);
+    ifNonNull(from.getUpdateTime(), Timestamps::toMillis, toBuilder::setUpdateTime);
+    ifNonNull(from.getCreateTime(), Timestamps::toMillis, toBuilder::setCreateTime);
+    ifNonNull(from.getCustomTime(), Timestamps::toMillis, toBuilder::setCustomTime);
+    ifNonNull(
+        from.getCustomerEncryption(),
+        customerEncryptionCodec::decode,
+        toBuilder::setCustomerEncryption);
+    ifNonNull(from.getStorageClass(), StorageClass::valueOf, toBuilder::setStorageClass);
+    ifNonNull(
+        from.getUpdateStorageClassTime(),
+        Timestamps::toMillis,
+        toBuilder::setTimeStorageClassUpdated);
+    ifNonNull(from.getKmsKey(), toBuilder::setKmsKeyName);
+    ifNonNull(from.getEventBasedHold(), toBuilder::setEventBasedHold);
+    ifNonNull(from.getTemporaryHold(), toBuilder::setTemporaryHold);
+    ifNonNull(
+        from.getRetentionExpireTime(), Timestamps::toMillis, toBuilder::setRetentionExpirationTime);
+    return toBuilder.build();
+  }
+
+  private int crc32cDecode(String from) {
+    byte[] decodeCrc32c = BaseEncoding.base64().decode(from);
+    return Ints.fromByteArray(decodeCrc32c);
+  }
+
+  private String crc32cEncode(int from) {
+    return BaseEncoding.base64().encode(Ints.toByteArray(from));
+  }
 }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java
index ef89b8ea..05b07b26 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java
@@ -33,6 +33,7 @@ import com.google.cloud.storage.PostPolicyV4.PostFieldsV4;
 import com.google.cloud.storage.spi.v1.StorageRpc;
 import com.google.storage.v2.DeleteHmacKeyRequest;
 import com.google.storage.v2.GetBucketRequest;
+import com.google.storage.v2.GetObjectRequest;
 import com.google.storage.v2.GetServiceAccountRequest;
 import com.google.storage.v2.stub.GrpcStorageStub;
 import java.io.IOException;
@@ -133,17 +134,31 @@ final class GrpcStorageImpl extends BaseService<StorageOptions> implements Stora
 
   @Override
   public Blob get(String bucket, String blob, BlobGetOption... options) {
-    return todo();
+    return get(BlobId.of(bucket, blob), options);
   }
 
   @Override
   public Blob get(BlobId blob, BlobGetOption... options) {
-    return todo();
+    UnaryCallable<GetObjectRequest, com.google.storage.v2.Object> unaryCallable =
+        grpcStorageStub.getObjectCallable();
+    final Map<StorageRpc.Option, ?> optionsMap = StorageImpl.optionMap(options);
+    GetObjectRequest.Builder getObjectRequestBuilder =
+        GetObjectRequest.newBuilder().setBucket(blob.getBucket()).setObject(blob.getName());
+    ifNonNull(
+        (Long) optionsMap.get(StorageRpc.Option.IF_METAGENERATION_MATCH),
+        getObjectRequestBuilder::setIfMetagenerationMatch);
+    ifNonNull(
+        (Long) optionsMap.get(StorageRpc.Option.IF_METAGENERATION_NOT_MATCH),
+        getObjectRequestBuilder::setIfMetagenerationNotMatch);
+    // TODO(sydmunro) StorageRpc.Option.Fields
+    com.google.storage.v2.Object object = unaryCallable.call(getObjectRequestBuilder.build());
+    BlobInfo blobInfo = codecs.blobInfo().decode(object);
+    return blobInfo.asBlob(this);
   }
 
   @Override
   public Blob get(BlobId blob) {
-    return todo();
+    return get(blob, new BlobGetOption[0]);
   }
 
   @Override
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/HmacKey.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/HmacKey.java
index 5291869d..26b02257 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/HmacKey.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/HmacKey.java
@@ -16,7 +16,10 @@
 
 package com.google.cloud.storage;
 
+import static com.google.cloud.storage.BackwardCompatibilityUtils.millisOffsetDateTimeCodec;
+
 import java.io.Serializable;
+import java.time.OffsetDateTime;
 import java.util.Objects;
 
 /** HMAC key for a service account. */
@@ -113,8 +116,8 @@ public class HmacKey implements Serializable {
     private final String projectId;
     private final ServiceAccount serviceAccount;
     private final HmacKeyState state;
-    private final Long createTime;
-    private final Long updateTime;
+    private final OffsetDateTime createTime;
+    private final OffsetDateTime updateTime;
 
     private HmacKeyMetadata(Builder builder) {
       this.accessId = builder.accessId;
@@ -200,13 +203,33 @@ public class HmacKey implements Serializable {
       return state;
     }
 
-    /** Returns the creation time of this HMAC key. * */
+    /**
+     * Returns the creation time of this HMAC key.
+     *
+     * @deprecated Use {@link #getCreateTimeOffsetDateTime()}
+     */
+    @Deprecated
     public Long getCreateTime() {
+      return millisOffsetDateTimeCodec.decode(createTime);
+    }
+
+    /** Returns the creation time of this HMAC key. * */
+    public OffsetDateTime getCreateTimeOffsetDateTime() {
       return createTime;
     }
 
-    /** Returns the last updated time of this HMAC key. * */
+    /**
+     * Returns the last updated time of this HMAC key.
+     *
+     * @deprecated Use {@link #getUpdateTimeOffsetDateTime()}
+     */
+    @Deprecated
     public Long getUpdateTime() {
+      return millisOffsetDateTimeCodec.decode(updateTime);
+    }
+
+    /** Returns the last updated time of this HMAC key. * */
+    public OffsetDateTime getUpdateTimeOffsetDateTime() {
       return updateTime;
     }
 
@@ -218,8 +241,8 @@ public class HmacKey implements Serializable {
       private String projectId;
       private ServiceAccount serviceAccount;
       private HmacKeyState state;
-      private Long createTime;
-      private Long updateTime;
+      private OffsetDateTime createTime;
+      private OffsetDateTime updateTime;
 
       private Builder(ServiceAccount serviceAccount) {
         this.serviceAccount = serviceAccount;
@@ -261,7 +284,13 @@ public class HmacKey implements Serializable {
         return this;
       }
 
+      /** @deprecated Use {@link #setCreateTimeOffsetDateTime(OffsetDateTime)} */
+      @Deprecated
       public Builder setCreateTime(long createTime) {
+        return setCreateTimeOffsetDateTime(millisOffsetDateTimeCodec.encode(createTime));
+      }
+
+      public Builder setCreateTimeOffsetDateTime(OffsetDateTime createTime) {
         this.createTime = createTime;
         return this;
       }
@@ -276,7 +305,13 @@ public class HmacKey implements Serializable {
         return new HmacKeyMetadata(this);
       }
 
+      /** @deprecated Use {@link #setUpdateTimeOffsetDateTime(OffsetDateTime)} */
+      @Deprecated
       public Builder setUpdateTime(long updateTime) {
+        return setUpdateTimeOffsetDateTime(millisOffsetDateTimeCodec.encode(updateTime));
+      }
+
+      public Builder setUpdateTimeOffsetDateTime(OffsetDateTime updateTime) {
         this.updateTime = updateTime;
         return this;
       }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java
index af449e3d..1959a274 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java
@@ -17,7 +17,9 @@
 package com.google.cloud.storage;
 
 import com.google.api.core.InternalApi;
+import com.google.cloud.storage.Conversions.Codec;
 import com.google.common.collect.ImmutableList;
+import java.time.Duration;
 import java.time.format.DateTimeFormatter;
 import java.util.List;
 import java.util.function.Consumer;
@@ -35,6 +37,8 @@ final class Utils {
 
   static final DateTimeFormatter RFC_3339_DATE_TIME_FORMATTER =
       DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
+  static final Codec<Duration, Long> durationMillisCodec =
+      Codec.of(Duration::toMillis, Duration::ofMillis);
 
   private Utils() {}
 


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoPropertyTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoPropertyTest.java
new file mode 100644
index 00000000..0de3c53d
--- /dev/null
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoPropertyTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed 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 com.google.cloud.storage;
+
+import com.google.cloud.storage.Conversions.Codec;
+import com.google.storage.v2.Object;
+
+final class BlobInfoPropertyTest extends BaseConvertablePropertyTest<BlobInfo, Object> {
+
+  @Override
+  Codec<BlobInfo, Object> codec() {
+    return Conversions.grpc().blobInfo();
+  }
+}
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/DateTimeCodecPropertyTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/DateTimeCodecPropertyTest.java
index 27079f8d..82b8190a 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/DateTimeCodecPropertyTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/DateTimeCodecPropertyTest.java
@@ -34,7 +34,7 @@ import net.jqwik.time.api.DateTimes;
 
 final class DateTimeCodecPropertyTest {
 
-  private static final Codec<DateTime, OffsetDateTime> codec =
+  private static final Codec<OffsetDateTime, DateTime> codec =
       ApiaryConversions.INSTANCE.dateTimeCodec;
 
   @Example
@@ -55,8 +55,8 @@ final class DateTimeCodecPropertyTest {
   @Property(tries = 10000)
   void codecShouldRoundTrip(@ForAll("rfc3339") String rfc3339String) {
     DateTime actual = new DateTime(rfc3339String);
-    OffsetDateTime odt = codec.encode(actual);
-    DateTime dt = codec.decode(odt);
+    OffsetDateTime odt = codec.decode(actual);
+    DateTime dt = codec.encode(odt);
 
     assertThat(dt.toStringRfc3339()).isEqualTo(rfc3339String);
   }
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/jqwik/ObjectArbitraryProvider.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/jqwik/ObjectArbitraryProvider.java
index da3e0221..a7eac053 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/jqwik/ObjectArbitraryProvider.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/jqwik/ObjectArbitraryProvider.java
@@ -16,11 +16,13 @@
 
 package com.google.cloud.storage.jqwik;
 
-import static com.google.cloud.storage.PackagePrivateMethodWorkarounds.todo;
-
 import com.google.storage.v2.Object;
+import java.util.Collections;
 import java.util.Set;
+import net.jqwik.api.Arbitraries;
 import net.jqwik.api.Arbitrary;
+import net.jqwik.api.Combinators;
+import net.jqwik.api.Tuple;
 import net.jqwik.api.providers.ArbitraryProvider;
 import net.jqwik.api.providers.TypeUsage;
 
@@ -33,6 +35,69 @@ public final class ObjectArbitraryProvider implements ArbitraryProvider {
 
   @Override
   public Set<Arbitrary<?>> provideFor(TypeUsage targetType, SubtypeProvider subtypeProvider) {
-    return todo();
+    Arbitrary<String> objectName = StorageArbitraries.randomString();
+    Arbitrary<Integer> size = Arbitraries.integers().greaterOrEqual(0);
+    Arbitrary<Object> objectArbitrary =
+        Combinators.combine(
+                Combinators.combine(
+                        objectName,
+                        StorageArbitraries.bucketName(),
+                        StorageArbitraries.generation(),
+                        StorageArbitraries.metageneration(),
+                        StorageArbitraries.objects().storageClass(),
+                        size,
+                        StorageArbitraries.randomString(),
+                        StorageArbitraries.randomString())
+                    .as(Tuple::of),
+                Combinators.combine(
+                        StorageArbitraries.randomString(),
+                        StorageArbitraries.randomString(),
+                        StorageArbitraries.timestamp(),
+                        StorageArbitraries.randomString(),
+                        StorageArbitraries.timestamp(),
+                        Arbitraries.integers().greaterOrEqual(0),
+                        StorageArbitraries.objects().objectChecksumsArbitrary())
+                    .as(Tuple::of),
+                Combinators.combine(
+                        StorageArbitraries.timestamp(),
+                        StorageArbitraries.randomString(),
+                        StorageArbitraries.timestamp(),
+                        StorageArbitraries.bool(),
+                        StorageArbitraries.timestamp(),
+                        StorageArbitraries.bool(),
+                        StorageArbitraries.objects().customerEncryptionArbitrary(),
+                        StorageArbitraries.timestamp())
+                    .as(Tuple::of))
+            .as(
+                (t1, t2, t3) ->
+                    Object.newBuilder()
+                        .setName(t1.get1())
+                        .setBucket(t1.get2().get())
+                        .setGeneration(t1.get3())
+                        .setMetageneration(t1.get4())
+                        .setStorageClass(t1.get5())
+                        .setSize(t1.get6())
+                        .setContentEncoding(t1.get7())
+                        .setContentDisposition(t1.get8())
+                        .setCacheControl(t2.get1())
+                        // TODO: Object ACLs
+                        .setContentLanguage(t2.get2())
+                        .setDeleteTime(t2.get3())
+                        .setContentType(t2.get4())
+                        .setCreateTime(t2.get5())
+                        .setComponentCount(t2.get6())
+                        .setChecksums(t2.get7())
+                        .setUpdateTime(t3.get1())
+                        .setKmsKey(t3.get2())
+                        .setUpdateStorageClassTime(t3.get3())
+                        .setTemporaryHold(t3.get4())
+                        .setRetentionExpireTime(t3.get5())
+                        // TODO: User Metadata
+                        .setEventBasedHold(t3.get6())
+                        // TODO: Owner
+                        .setCustomerEncryption(t3.get7())
+                        .setCustomTime(t3.get8())
+                        .build());
+    return Collections.singleton(objectArbitrary);
   }
 }
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/jqwik/StorageArbitraries.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/jqwik/StorageArbitraries.java
index c04d5f27..e38dd538 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/jqwik/StorageArbitraries.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/jqwik/StorageArbitraries.java
@@ -16,6 +16,9 @@
 
 package com.google.cloud.storage.jqwik;
 
+import com.google.common.hash.Hashing;
+import com.google.common.io.BaseEncoding;
+import com.google.protobuf.ByteString;
 import com.google.protobuf.Timestamp;
 import com.google.protobuf.util.Timestamps;
 import com.google.storage.v2.Bucket;
@@ -24,6 +27,9 @@ import com.google.storage.v2.Bucket.Encryption;
 import com.google.storage.v2.Bucket.RetentionPolicy;
 import com.google.storage.v2.Bucket.Versioning;
 import com.google.storage.v2.Bucket.Website;
+import com.google.storage.v2.CustomerEncryption;
+import com.google.storage.v2.ObjectChecksums;
+import java.nio.charset.StandardCharsets;
 import net.jqwik.api.Arbitraries;
 import net.jqwik.api.Arbitrary;
 import net.jqwik.api.Combinators;
@@ -45,6 +51,14 @@ public final class StorageArbitraries {
     return Arbitraries.longs().greaterOrEqual(0);
   }
 
+  public static Arbitrary<Long> generation() {
+    return Arbitraries.longs().greaterOrEqual(0);
+  }
+
+  public static Arbitrary<String> randomString() {
+    return Arbitraries.strings().all().ofMinLength(1).ofMaxLength(1024);
+  }
+
   /**
    * Generated bucket name based on the rules outlined in <a target="_blank" rel="noopener
    * noreferrer"
@@ -160,6 +174,7 @@ public final class StorageArbitraries {
   }
 
   public static final class ProjectID {
+
     private final String value;
 
     private ProjectID(String value) {
@@ -170,4 +185,49 @@ public final class StorageArbitraries {
       return value;
     }
   }
+
+  public static Objects objects() {
+    return Objects.INSTANCE;
+  }
+
+  public static final class Objects {
+    private static final Objects INSTANCE = new Objects();
+
+    private Objects() {}
+
+    public Arbitrary<String> storageClass() {
+      // TODO: return each of the real values and edge cases (including invalid values)
+      return Arbitraries.strings().all().ofMinLength(1).ofLength(1024);
+    }
+
+    public Arbitrary<ObjectChecksums> objectChecksumsArbitrary() {
+      return Combinators.combine(
+              Arbitraries.integers().greaterOrEqual(1),
+              Arbitraries.strings()
+                  .map(
+                      s ->
+                          BaseEncoding.base64()
+                              .encode(Hashing.md5().hashBytes(s.getBytes()).asBytes())))
+          .as(
+              (crc32c, md5) ->
+                  ObjectChecksums.newBuilder()
+                      .setCrc32C(crc32c)
+                      .setMd5Hash(ByteString.copyFrom(md5.getBytes()))
+                      .build());
+    }
+
+    public Arbitrary<CustomerEncryption> customerEncryptionArbitrary() {
+      return Combinators.combine(
+              Arbitraries.strings().ofMinLength(1).ofMaxLength(1024),
+              Arbitraries.strings()
+                  .map(s -> Hashing.sha256().hashString(s, StandardCharsets.UTF_8).asBytes())
+                  .map(ByteString::copyFrom))
+          .as(
+              (algorithm, key) ->
+                  CustomerEncryption.newBuilder()
+                      .setEncryptionAlgorithm(algorithm)
+                      .setKeySha256Bytes(key)
+                      .build());
+    }
+  }
 }