Index: trunk/src/com/drew/metadata/iptc/IptcDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/iptc/IptcDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/iptc/IptcDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,4 +26,6 @@
 import com.drew.metadata.TagDescriptor;
 
+import static com.drew.metadata.iptc.IptcDirectory.*;
+
 /**
  * Provides human-readable string representations of tag values stored in a {@link IptcDirectory}.
@@ -45,12 +47,30 @@
     {
         switch (tagType) {
-            case IptcDirectory.TAG_FILE_FORMAT:
+            case TAG_DATE_CREATED:
+                return getDateCreatedDescription();
+            case TAG_DIGITAL_DATE_CREATED:
+                return getDigitalDateCreatedDescription();
+            case TAG_DATE_SENT:
+                return getDateSentDescription();
+            case TAG_EXPIRATION_DATE:
+                return getExpirationDateDescription();
+            case TAG_EXPIRATION_TIME:
+                return getExpirationTimeDescription();
+            case TAG_FILE_FORMAT:
                 return getFileFormatDescription();
-            case IptcDirectory.TAG_KEYWORDS:
+            case TAG_KEYWORDS:
                 return getKeywordsDescription();
-            case IptcDirectory.TAG_TIME_CREATED:
+            case TAG_REFERENCE_DATE:
+                return getReferenceDateDescription();
+            case TAG_RELEASE_DATE:
+                return getReleaseDateDescription();
+            case TAG_RELEASE_TIME:
+                return getReleaseTimeDescription();
+            case TAG_TIME_CREATED:
                 return getTimeCreatedDescription();
-            case IptcDirectory.TAG_DIGITAL_TIME_CREATED:
+            case TAG_DIGITAL_TIME_CREATED:
                 return getDigitalTimeCreatedDescription();
+            case TAG_TIME_SENT:
+                return getTimeSentDescription();
             default:
                 return super.getDescription(tagType);
@@ -59,7 +79,29 @@
 
     @Nullable
+    public String getDateDescription(int tagType)
+    {
+        String s = _directory.getString(tagType);
+        if (s == null)
+            return null;
+        if (s.length() == 8)
+            return s.substring(0, 4) + ':' + s.substring(4, 6) + ':' + s.substring(6);
+        return s;
+    }
+
+    @Nullable
+    public String getTimeDescription(int tagType)
+    {
+        String s = _directory.getString(tagType);
+        if (s == null)
+            return null;
+        if (s.length() == 6 || s.length() == 11)
+            return s.substring(0, 2) + ':' + s.substring(2, 4) + ':' + s.substring(4);
+        return s;
+    }
+
+    @Nullable
     public String getFileFormatDescription()
     {
-        Integer value = _directory.getInteger(IptcDirectory.TAG_FILE_FORMAT);
+        Integer value = _directory.getInteger(TAG_FILE_FORMAT);
         if (value == null)
             return null;
@@ -102,5 +144,5 @@
     public String getByLineDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_BY_LINE);
+        return _directory.getString(TAG_BY_LINE);
     }
 
@@ -108,5 +150,5 @@
     public String getByLineTitleDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_BY_LINE_TITLE);
+        return _directory.getString(TAG_BY_LINE_TITLE);
     }
 
@@ -114,5 +156,5 @@
     public String getCaptionDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_CAPTION);
+        return _directory.getString(TAG_CAPTION);
     }
 
@@ -120,5 +162,5 @@
     public String getCategoryDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_CATEGORY);
+        return _directory.getString(TAG_CATEGORY);
     }
 
@@ -126,5 +168,5 @@
     public String getCityDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_CITY);
+        return _directory.getString(TAG_CITY);
     }
 
@@ -132,5 +174,5 @@
     public String getCopyrightNoticeDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_COPYRIGHT_NOTICE);
+        return _directory.getString(TAG_COPYRIGHT_NOTICE);
     }
 
@@ -138,5 +180,5 @@
     public String getCountryOrPrimaryLocationDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_COUNTRY_OR_PRIMARY_LOCATION_NAME);
+        return _directory.getString(TAG_COUNTRY_OR_PRIMARY_LOCATION_NAME);
     }
 
@@ -144,5 +186,5 @@
     public String getCreditDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_CREDIT);
+        return _directory.getString(TAG_CREDIT);
     }
 
@@ -150,5 +192,29 @@
     public String getDateCreatedDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_DATE_CREATED);
+        return getDateDescription(TAG_DATE_CREATED);
+    }
+
+    @Nullable
+    public String getDigitalDateCreatedDescription()
+    {
+        return getDateDescription(TAG_DIGITAL_DATE_CREATED);
+    }
+
+    @Nullable
+    public String getDateSentDescription()
+    {
+        return getDateDescription(TAG_DATE_SENT);
+    }
+
+    @Nullable
+    public String getExpirationDateDescription()
+    {
+        return getDateDescription(TAG_EXPIRATION_DATE);
+    }
+
+    @Nullable
+    public String getExpirationTimeDescription()
+    {
+        return getTimeDescription(TAG_EXPIRATION_TIME);
     }
 
@@ -156,5 +222,5 @@
     public String getHeadlineDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_HEADLINE);
+        return _directory.getString(TAG_HEADLINE);
     }
 
@@ -162,5 +228,5 @@
     public String getKeywordsDescription()
     {
-        final String[] keywords = _directory.getStringArray(IptcDirectory.TAG_KEYWORDS);
+        final String[] keywords = _directory.getStringArray(TAG_KEYWORDS);
         if (keywords==null)
             return null;
@@ -171,5 +237,5 @@
     public String getObjectNameDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_OBJECT_NAME);
+        return _directory.getString(TAG_OBJECT_NAME);
     }
 
@@ -177,5 +243,5 @@
     public String getOriginalTransmissionReferenceDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_ORIGINAL_TRANSMISSION_REFERENCE);
+        return _directory.getString(TAG_ORIGINAL_TRANSMISSION_REFERENCE);
     }
 
@@ -183,5 +249,5 @@
     public String getOriginatingProgramDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_ORIGINATING_PROGRAM);
+        return _directory.getString(TAG_ORIGINATING_PROGRAM);
     }
 
@@ -189,5 +255,5 @@
     public String getProvinceOrStateDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_PROVINCE_OR_STATE);
+        return _directory.getString(TAG_PROVINCE_OR_STATE);
     }
 
@@ -195,5 +261,11 @@
     public String getRecordVersionDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_APPLICATION_RECORD_VERSION);
+        return _directory.getString(TAG_APPLICATION_RECORD_VERSION);
+    }
+
+    @Nullable
+    public String getReferenceDateDescription()
+    {
+        return getDateDescription(TAG_REFERENCE_DATE);
     }
 
@@ -201,5 +273,5 @@
     public String getReleaseDateDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_RELEASE_DATE);
+        return getDateDescription(TAG_RELEASE_DATE);
     }
 
@@ -207,5 +279,5 @@
     public String getReleaseTimeDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_RELEASE_TIME);
+        return getTimeDescription(TAG_RELEASE_TIME);
     }
 
@@ -213,5 +285,5 @@
     public String getSourceDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_SOURCE);
+        return _directory.getString(TAG_SOURCE);
     }
 
@@ -219,5 +291,5 @@
     public String getSpecialInstructionsDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_SPECIAL_INSTRUCTIONS);
+        return _directory.getString(TAG_SPECIAL_INSTRUCTIONS);
     }
 
@@ -225,5 +297,5 @@
     public String getSupplementalCategoriesDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_SUPPLEMENTAL_CATEGORIES);
+        return _directory.getString(TAG_SUPPLEMENTAL_CATEGORIES);
     }
 
@@ -231,10 +303,5 @@
     public String getTimeCreatedDescription()
     {
-        String s = _directory.getString(IptcDirectory.TAG_TIME_CREATED);
-        if (s == null)
-            return null;
-        if (s.length() == 6 || s.length() == 11)
-            return s.substring(0, 2) + ':' + s.substring(2, 4) + ':' + s.substring(4);
-        return s;
+        return getTimeDescription(TAG_TIME_CREATED);
     }
 
@@ -242,10 +309,11 @@
     public String getDigitalTimeCreatedDescription()
     {
-        String s = _directory.getString(IptcDirectory.TAG_DIGITAL_TIME_CREATED);
-        if (s == null)
-            return null;
-        if (s.length() == 6 || s.length() == 11)
-            return s.substring(0, 2) + ':' + s.substring(2, 4) + ':' + s.substring(4);
-        return s;
+        return getTimeDescription(TAG_DIGITAL_TIME_CREATED);
+    }
+
+    @Nullable
+    public String getTimeSentDescription()
+    {
+        return getTimeDescription(TAG_TIME_SENT);
     }
 
@@ -253,5 +321,5 @@
     public String getUrgencyDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_URGENCY);
+        return _directory.getString(TAG_URGENCY);
     }
 
@@ -259,5 +327,5 @@
     public String getWriterDescription()
     {
-        return _directory.getString(IptcDirectory.TAG_CAPTION_WRITER);
+        return _directory.getString(TAG_CAPTION_WRITER);
     }
 }
Index: trunk/src/com/drew/metadata/iptc/IptcDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/iptc/IptcDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/iptc/IptcDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,5 +25,9 @@
 import com.drew.metadata.Directory;
 
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
@@ -231,8 +235,83 @@
     public List<String> getKeywords()
     {
-        final String[] array = getStringArray(IptcDirectory.TAG_KEYWORDS);
+        final String[] array = getStringArray(TAG_KEYWORDS);
         if (array==null)
             return null;
         return Arrays.asList(array);
     }
+
+    /**
+     * Parses the Date Sent tag and the Time Sent tag to obtain a single Date object representing the
+     * date and time when the service sent this image.
+     * @return A Date object representing when the service sent this image, if possible, otherwise null
+     */
+    @Nullable
+    public Date getDateSent()
+    {
+        return getDate(TAG_DATE_SENT, TAG_TIME_SENT);
+    }
+
+    /**
+     * Parses the Release Date tag and the Release Time tag to obtain a single Date object representing the
+     * date and time when this image was released.
+     * @return A Date object representing when this image was released, if possible, otherwise null
+     */
+    @Nullable
+    public Date getReleaseDate()
+    {
+        return getDate(TAG_RELEASE_DATE, TAG_RELEASE_TIME);
+    }
+
+    /**
+     * Parses the Expiration Date tag and the Expiration Time tag to obtain a single Date object representing
+     * that this image should not used after this date and time.
+     * @return A Date object representing when this image was released, if possible, otherwise null
+     */
+    @Nullable
+    public Date getExpirationDate()
+    {
+        return getDate(TAG_EXPIRATION_DATE, TAG_EXPIRATION_TIME);
+    }
+
+    /**
+     * Parses the Date Created tag and the Time Created tag to obtain a single Date object representing the
+     * date and time when this image was captured.
+     * @return A Date object representing when this image was captured, if possible, otherwise null
+     */
+    @Nullable
+    public Date getDateCreated()
+    {
+        return getDate(TAG_DATE_CREATED, TAG_TIME_CREATED);
+    }
+
+    /**
+     * Parses the Digital Date Created tag and the Digital Time Created tag to obtain a single Date object
+     * representing the date and time when the digital representation of this image was created.
+     * @return A Date object representing when the digital representation of this image was created,
+     * if possible, otherwise null
+     */
+    @Nullable
+    public Date getDigitalDateCreated()
+    {
+        return getDate(TAG_DIGITAL_DATE_CREATED, TAG_DIGITAL_TIME_CREATED);
+    }
+
+    @Nullable
+    private Date getDate(int dateTagType, int timeTagType)
+    {
+        String date = getString(dateTagType);
+        String time = getString(timeTagType);
+
+        if (date == null)
+            return null;
+        if (time == null)
+            return null;
+
+        try {
+            DateFormat parser = new SimpleDateFormat("yyyyMMddHHmmssZ");
+            return parser.parse(date + time);
+        } catch (ParseException e) {
+            return null;
+        }
+    }
 }
Index: trunk/src/com/drew/metadata/iptc/IptcReader.java
===================================================================
--- trunk/src/com/drew/metadata/iptc/IptcReader.java	(revision 8243)
+++ trunk/src/com/drew/metadata/iptc/IptcReader.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,10 +26,10 @@
 import com.drew.lang.SequentialReader;
 import com.drew.lang.annotations.NotNull;
+import com.drew.lang.annotations.Nullable;
 import com.drew.metadata.Directory;
 import com.drew.metadata.Metadata;
 
 import java.io.IOException;
-import java.util.Arrays;
-import java.util.Date;
+import java.util.Collections;
 
 /**
@@ -60,5 +60,5 @@
     public Iterable<JpegSegmentType> getSegmentTypes()
     {
-        return Arrays.asList(JpegSegmentType.APPD);
+        return Collections.singletonList(JpegSegmentType.APPD);
     }
 
@@ -78,6 +78,17 @@
     public void extract(@NotNull final SequentialReader reader, @NotNull final Metadata metadata, long length)
     {
+        extract(reader, metadata, length, null);
+    }
+
+    /**
+     * Performs the IPTC data extraction, adding found values to the specified instance of {@link Metadata}.
+     */
+    public void extract(@NotNull final SequentialReader reader, @NotNull final Metadata metadata, long length, @Nullable Directory parentDirectory)
+    {
         IptcDirectory directory = new IptcDirectory();
         metadata.addDirectory(directory);
+
+        if (parentDirectory != null)
+            directory.setParent(parentDirectory);
 
         int offset = 0;
@@ -105,5 +116,5 @@
 
             // we need at least five bytes left to read a tag
-            if (offset + 5 >= length) {
+            if (offset + 5 > length) {
                 directory.addError("Too few bytes remain for a valid IPTC tag");
                 return;
@@ -184,25 +195,4 @@
                 reader.skip(tagByteCount - 1);
                 return;
-            case IptcDirectory.TAG_RELEASE_DATE:
-            case IptcDirectory.TAG_DATE_CREATED:
-                // Date object
-                if (tagByteCount >= 8) {
-                    string = reader.getString(tagByteCount);
-                    try {
-                        int year = Integer.parseInt(string.substring(0, 4));
-                        int month = Integer.parseInt(string.substring(4, 6)) - 1;
-                        int day = Integer.parseInt(string.substring(6, 8));
-                        Date date = new java.util.GregorianCalendar(year, month, day).getTime();
-                        directory.setDate(tagIdentifier, date);
-                        return;
-                    } catch (NumberFormatException e) {
-                        // fall through and we'll process the 'string' value below
-                    }
-                } else {
-                    reader.skip(tagByteCount);
-                }
-            case IptcDirectory.TAG_RELEASE_TIME:
-            case IptcDirectory.TAG_TIME_CREATED:
-                // time...
             default:
                 // fall through
@@ -227,4 +217,5 @@
             String[] newStrings;
             if (oldStrings == null) {
+                // TODO hitting this block means any prior value(s) are discarded
                 newStrings = new String[1];
             } else {
Index: trunk/src/com/drew/metadata/iptc/Iso2022Converter.java
===================================================================
--- trunk/src/com/drew/metadata/iptc/Iso2022Converter.java	(revision 8243)
+++ trunk/src/com/drew/metadata/iptc/Iso2022Converter.java	(revision 10862)
@@ -1,2 +1,22 @@
+/*
+ * Copyright 2002-2016 Drew Noakes
+ *
+ *    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.
+ *
+ * More information about this project is available at:
+ *
+ *    https://drewnoakes.com/code/exif/
+ *    https://github.com/drewnoakes/metadata-extractor
+ */
 package com.drew.metadata.iptc;
 
@@ -40,5 +60,5 @@
     /**
      * Attempts to guess the encoding of a string provided as a byte array.
-     * <p/>
+     * <p>
      * Encodings trialled are, in order:
      * <ul>
@@ -47,9 +67,9 @@
      *     <li>ISO-8859-1</li>
      * </ul>
-     * <p/>
+     * <p>
      * Its only purpose is to guess the encoding if and only if iptc tag coded character set is not set. If the
      * encoding is not UTF-8, the tag should be set. Otherwise it is bad practice. This method tries to
      * workaround this issue since some metadata manipulating tools do not prevent such bad practice.
-     * <p/>
+     * <p>
      * About the reliability of this method: The check if some bytes are UTF-8 or not has a very high reliability.
      * The two other checks are less reliable.
Index: trunk/src/com/drew/metadata/iptc/package.html
===================================================================
--- trunk/src/com/drew/metadata/iptc/package.html	(revision 8243)
+++ trunk/src/com/drew/metadata/iptc/package.html	(revision 10862)
@@ -1,4 +1,4 @@
 <!--
-  ~ Copyright 2002-2015 Drew Noakes
+  ~ Copyright 2002-2016 Drew Noakes
   ~
   ~    Licensed under the Apache License, Version 2.0 (the "License");
