/*
* Copyright 2006-2017 ICEsoft Technologies Canada Corp.
*
* 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 org.icepdf.core.pobjects;
import org.icepdf.core.pobjects.security.SecurityManager;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* <p>This class defines a standard PDF date. The class will try its best
* to parse the date format into its component parts. If a date cannot
* be parsed, a non-standard flag is set to true. In this instance, any
* of the data accessor methods will return the unparsed string.</p>
* <br>
* <p>PDF defines a standard date format, which closely follows that of the
* international standard ASN.1 (Abstract Syntax Notation One), defined in
* ISO/IEC 8824. A date is a string of the form (D:YYYYMMDDHHmmSSOHH'mm')
* where:
* <ul>
* <li>YYYY is the year</li>
* <li>MM is the month </li>
* <li>DD is the day (01-31)</li>
* <li>HH is the hour (00-23) </li>
* <li>mm is the minute (00-59) </li>
* <li>SS is the second (00-59) </li>
* <li>O is the relationship of local time to Universal Time (UT), denoted by
* one of the characters +, ?, or Z (see below) </li>
* <li>HH followed by ' is the absolute value of the offset from UT in hours
* (00-23)mm followed by ' is the absolute value of the offset from UT
* in minutes (00-59)</li>
* </ul>
* <br>
* <p>The apostrophe character (') after HH and mm is part of the syntax. All
* fields after the year are optional. (The prefix D:, although also optional,
* is strongly recommended.) The default values for MM and DD are both 01; all
* other numerical fields default to zero values. A plus sign (+) as the value
* of the O field signifies that local time is later than UT, a minus sign (-)
* that local time is earlier than UT, and the letter Z that local time is equal
* to UT. If no UT information is specified, the relationship of the specified
* time to UT is considered to be unknown. Whether or not the time zone is
* known, the rest of the date should be specified in local time. For example,
* December 23, 1998, at 7:52 PM, U.S. Pacific Standard Time, is represented by
* the string D:199812231952?08'00'</p>
*
* @since 1.1
*/
public class PDate {
protected static final SimpleDateFormat DATE_FORMAT;
static {
DATE_FORMAT = new SimpleDateFormat("'D:'yyyyMMddHHmmss");
DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
}
// offset value for year, YYYY
private static final int OFFSET_YYYY = 4;
// offset value for month, MM
private static final int OFFSET_MM = 2;
// offset value for day, DD
private static final int OFFSET_DD = 2;
// offset value for hour, HH
private static final int OFFSET_HH = 2;
// offset value for minute, mm
private static final int OFFSET_mm = 2;
// offset value for second, SS
private static final int OFFSET_SS = 2;
// offset value for east or west of GMT, 0
private static final int OFFSET_0 = 1;
// Optional prefix for a date
private static final String DATE_PREFIX = "D:";
// Month Names, 1 indexed based
private static String[] monthNames = {"",
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"};
// instance values related to time parts
private String year = "";
private String month = "";
private String day = "";
private String hour = "";
private String minute = "";
private String second = "";
private String timeZoneOffset = "";
private String timeZoneHour = "";
private String timeZoneMinute = "";
// set to true when an non standard data is encountered
private boolean notStandardFormat = false;
/**
* Create a new Date object.
*
* @param date date ASCII data.
*/
public PDate(SecurityManager securityManager, String date) {
// parse the the date string
if (date != null) {
parseDate(date);
}
}
/**
* Gets the year value of the date.
* <br><b>Note</b><br>
* If the original date value cannot be parsed, this method returns
* the unparsed string.
*
* @return year value.
*/
public String getYear() {
return year;
}
/**
* Gets the month value of the date.
* <br><b>Note</b><br>
* If the original date value cannot be parsed, this method returns
* the unparsed string.
*
* @return month value.
*/
public String getMonth() {
return month;
}
/**
* Gets the day value of the date.
* <br><b>Note</b><br>
* If the original date value cannot be parsed, this method returns
* the unparsed string.
*
* @return day value.
*/
public String getDay() {
return day;
}
/**
* Gets the hour value of the date.
* <br><b>Note</b><br>
* If the original date value cannot be parsed, this method returns
* the unparsed string.
*
* @return hour value.
*/
public String getHour() {
return hour;
}
/**
* Gets the minute value of the date.
* <br><b>Note</b><br>
* If the original date value cannot be parsed, this method returns
* the unparsed string.
*
* @return minute value.
*/
public String getMinute() {
return minute;
}
/**
* Gets the second value of the date.
* <br><b>Note</b><br>
* If the original date value cannot be parsed, this method returns
* the unparsed string.
*
* @return second value.
*/
public String getSecond() {
return second;
}
/**
* Gets the time zone offset hour from GMT.
* <br><b>Note</b><br>
* If the original date value cannot be parsed, this method returns
* the unparsed string.
*
* @return hour value.
*/
public String getTimeZoneHour() {
return timeZoneHour;
}
/**
* Gets the time zone offset minute from GMT.
* <br><b>Note</b><br>
* If the original date value cannot be parsed, this method returns
* the unparsed string.
*
* @return minute value.
*/
public String getTimeZoneMinute() {
return timeZoneMinute;
}
/**
* Gets the time zone offset fromm GMT. If the offset is negative
* true is returned, false otherwise.
* <br><b>Note</b><br>
* If the original date value cannot be parsed, this method returns
* the unparsed string.
*
* @return time offset value.
*/
public boolean getTimeZoneOffset() {
return !"-".equals(timeZoneOffset);
}
/**
* Returns a decoded string representation of the date object.
* If the date object could not be parsed, the orginal date value is
* returned.
*
* @return date value.
*/
public String toString() {
if (!notStandardFormat) {
StringBuilder sb = new StringBuilder(40);
if (getMonth(month).length() > 0)
sb.append(getMonth(month));
if (day.length() > 0)
sb.append(" ").append(day);
if (year.length() > 0)
sb.append(", ").append(year);
if (hour.length() > 0)
sb.append(" ").append(hour);
if (minute.length() > 0)
sb.append(":").append(minute);
if (second.length() > 0)
sb.append(":").append(second);
if (timeZoneOffset.length() > 0) {
if (timeZoneOffset.equalsIgnoreCase("Z"))
sb.append(" (UTC)");
else {
sb.append(" (UTC ").append(timeZoneOffset);
if (timeZoneHour.length() > 0)
sb.append("").append(timeZoneHour);
if (timeZoneMinute.length() > 0)
sb.append(":").append(timeZoneMinute);
sb.append(")");
}
}
return sb.toString();
} else {
// return coded date
return day;
}
}
/**
* Utility method for parsing PDF docs date format
* (D:YYYYMMDDHHmmSSOHH'mm'). If the date is not in a know format
* all instances variables are assigned the date value.
*
* @param date string representing a PDF date object.
*/
private void parseDate(String date) {
// get ride of "D:" prefix
if (date.indexOf(DATE_PREFIX) >= 0) {
date = date.substring(2);
parseAdobeDate(date);
}
// have none standard form, Ghostscript, 5/26/2004 13:25:11
else if (date.indexOf("/") >= 0) {
parseGhostScriptDate(date);
}
//try adobe format but with out D:
else {
year = date;
month = date;
day = date;
hour = date;
minute = date;
second = date;
timeZoneOffset = date;
timeZoneHour = date;
timeZoneMinute = date;
notStandardFormat = true;
}
}
/**
* Utility method for parsing a ghostscript date formate, 5/26/2004 13:25:11.
*
* @param date string representing a PDF date object.
*/
private void parseGhostScriptDate(String date) {
// month/day/year hour:minute:second
// break the string on the date/time space
StringTokenizer dateTime = new StringTokenizer(date);
// tokenize the date using "/"
StringTokenizer dateToken =
new StringTokenizer(dateTime.nextToken(), "/");
// tokenize the time using ":"
StringTokenizer timeToken =
new StringTokenizer(dateTime.nextToken(), ":");
// get date vars
month = dateToken.nextToken();
day = dateToken.nextToken();
year = dateToken.nextToken();
// get time vars
hour = timeToken.nextToken();
minute = timeToken.nextToken();
second = timeToken.nextToken();
}
/**
* Utility mehtod for parsing Adobe standard date format,
* (D:YYYYMMDDHHmmSSOHH'mm').
*
* @param date string representing a PDF date object.
*/
private void parseAdobeDate(String date) {
// total offset count
int totalOffset = 0;
int currentOffset = 0;
// start peeling of values from string
if (totalOffset + OFFSET_YYYY <= date.length()) {
currentOffset = (totalOffset + OFFSET_YYYY);
year = date.substring(totalOffset, currentOffset);
totalOffset += currentOffset;
}
if (totalOffset + OFFSET_MM <= date.length()) {
currentOffset = (totalOffset + OFFSET_MM);
month = date.substring(totalOffset, currentOffset);
totalOffset += OFFSET_MM;
}
if (totalOffset + OFFSET_DD <= date.length()) {
currentOffset = (totalOffset + OFFSET_DD);
day = date.substring(totalOffset, currentOffset);
totalOffset += OFFSET_DD;
}
if (totalOffset + OFFSET_HH <= date.length()) {
currentOffset = (totalOffset + OFFSET_HH);
hour = date.substring(totalOffset, currentOffset);
totalOffset += OFFSET_HH;
}
if (totalOffset + OFFSET_mm <= date.length()) {
currentOffset = (totalOffset + OFFSET_mm);
minute = date.substring(totalOffset, currentOffset);
totalOffset += OFFSET_mm;
}
if (totalOffset + OFFSET_SS <= date.length()) {
currentOffset = (totalOffset + OFFSET_SS);
second = date.substring(totalOffset, currentOffset);
totalOffset += OFFSET_SS;
}
if (totalOffset + OFFSET_0 <= date.length()) {
currentOffset = (totalOffset + OFFSET_0);
timeZoneOffset = date.substring(totalOffset, currentOffset);
totalOffset += OFFSET_0;
}
if (totalOffset + OFFSET_HH <= date.length()) {
currentOffset = (totalOffset + OFFSET_HH);
timeZoneHour = date.substring(totalOffset, currentOffset);
totalOffset += OFFSET_HH;
}
// lastly pull off 'MM' for timezone minutes
if (totalOffset + 4 <= date.length()) {
// compensate for the ' in 'MM'
timeZoneMinute = date.substring(totalOffset + 1, totalOffset + 3);
//System.out.println(timeZoneMinute);
}
}
/**
* Utility method for finding month names.
*/
private String getMonth(String month) {
int monthIndex = 0;
try {
monthIndex = Integer.parseInt(month);
}
// eat any problems
catch (NumberFormatException e) {
}
return monthNames[monthIndex];
}
/**
* Formats a date/time according to the PDF specification
* (D:YYYYMMDDHHmmSSOHH'mm').
*
* @param time date/time value to format
* @param tz the time zone
* @return the requested String representation
*/
public static String formatDateTime(Date time, TimeZone tz) {
Calendar cal = Calendar.getInstance(tz, Locale.ENGLISH);
cal.setTime(time);
int offset = cal.get(Calendar.ZONE_OFFSET);
offset += cal.get(Calendar.DST_OFFSET);
//DateFormat is operating on GMT so adjust for time zone offset
Date dt1 = new Date(time.getTime() + offset);
StringBuffer sb = new StringBuffer();
sb.append(DATE_FORMAT.format(dt1));
offset /= (1000 * 60); //Convert to minutes
if (offset == 0) {
sb.append('Z');
} else {
if (offset > 0) {
sb.append('+');
} else {
sb.append('-');
}
int offsetHour = Math.abs(offset / 60);
int offsetMinutes = Math.abs(offset % 60);
if (offsetHour < 10) {
sb.append('0');
}
sb.append(Integer.toString(offsetHour));
sb.append('\'');
if (offsetMinutes < 10) {
sb.append('0');
}
sb.append(Integer.toString(offsetMinutes));
sb.append('\'');
}
return sb.toString();
}
/**
* Formats a date/time according to the PDF specification.
* (D:YYYYMMDDHHmmSSOHH'mm').
*
* @param time date/time value to format
* @return the requested String representation
*/
public static String formatDateTime(Date time) {
return formatDateTime(time, TimeZone.getDefault());
}
public static PDate createDate(Date date) {
return new PDate(null, formatDateTime(date));
}
}