/** * Copyright Intellectual Reserve, Inc. * * 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.gedcomx.date; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.TimeZone; /** * A Simple Date * @author John Clark. */ public class GedcomxDateSimple extends GedcomxDate { private Integer year = null; private Integer month = null; private Integer day = null; private Integer hours = null; private Integer minutes = null; private Integer seconds = null; private Integer tzHours = null; private Integer tzMinutes = null; /** * Instantiate a new Simple date based off of a formal date string. * @param date The date */ public GedcomxDateSimple(String date) { parseDate(date); } /** * Parse the date portion of the formal string * @param date The date string */ private void parseDate(String date) { // There is a minimum length of 5 characters if(date.length() < 5) { throw new GedcomxDateException("Invalid Date: Must have at least [+-]YYYY"); } int end = date.length(); int offset = 0; String num; // Must start with a + or - if(date.charAt(offset) != '+' && date.charAt(offset) != '-') { throw new GedcomxDateException("Invalid Date: Must begin with + or -"); } offset++; num = date.charAt(0) == '-' ? "-" : ""; for(int i=0;i<4;i++) { if(!Character.isDigit(date.charAt(offset))) { throw new GedcomxDateException("Invalid Date: Malformed Year"); } num += date.charAt(offset++); } year = Integer.valueOf(num); if(offset == end) { return; } // If there is time if(date.charAt(offset) == 'T') { parseTime(date.substring(offset+1)); return; } // Month if(date.charAt(offset) != '-') { throw new GedcomxDateException("Invalid Date: Invalid Year-Month Separator"); } if(end-offset < 3) { throw new GedcomxDateException("Invalid Date: Month must be 2 digits"); } offset++; num = ""; for(int i=0;i<2;i++) { if(!Character.isDigit(date.charAt(offset))) { throw new GedcomxDateException("Invalid Date: Malformed Month"); } num += date.charAt(offset++); } month = Integer.valueOf(num); if(month < 1 || month > 12) { throw new GedcomxDateException("Invalid Date: Month must be between 1 and 12"); } if(offset == end) { return; } // If there is time if(date.charAt(offset) == 'T') { parseTime(date.substring(offset+1)); return; } // Day if(date.charAt(offset) != '-') { throw new GedcomxDateException("Invalid Date: Invalid Month-Day Separator"); } if(end-offset < 3) { throw new GedcomxDateException("Invalid Date: Day must be 2 digits"); } offset++; num = ""; for(int i=0;i<2;i++) { if(!Character.isDigit(date.charAt(offset))) { throw new GedcomxDateException("Invalid Date: Malformed Day"); } num += date.charAt(offset++); } day = Integer.valueOf(num); if(day < 1) { throw new GedcomxDateException("Invalid Date: Day 0 does not exist"); } int daysInMonth = GedcomxDateUtil.daysInMonth(month, year); if(day > daysInMonth) { throw new GedcomxDateException("Invalid Date: There are only "+daysInMonth+" days in Month "+month+" year "+year); } if(offset == end) { return; } if(date.charAt(offset) == 'T') { parseTime(date.substring(offset+1)); } else { throw new GedcomxDateException("Invalid Date: +YYYY-MM-DD must have T before time"); } } /** * Parse the time portion of the formal string * @param date The date string (minus the date) */ private void parseTime(String date) { int offset = 0; int end = date.length(); String num; boolean flag24 = false; // Always initialize the Timezone to the local offset. // It may be overridden if set TimeZone tz = TimeZone.getDefault(); Calendar cal = GregorianCalendar.getInstance(tz); int offsetInMillis = tz.getOffset(cal.getTimeInMillis()); tzHours = offsetInMillis / 3600000; tzMinutes = (offsetInMillis / 60000) % 60; // You must at least have hours if(end < 2) { throw new GedcomxDateException("Invalid Date: Hours must be 2 digits"); } num = ""; for(int i=0;i<2;i++) { if(!Character.isDigit(date.charAt(offset))) { throw new GedcomxDateException("Invalid Date: Malformed Hours"); } num += date.charAt(offset++); } hours = Integer.valueOf(num); if(hours > 24) { throw new GedcomxDateException("Invalid Date: Hours must be between 0 and 24"); } if(hours == 24) { flag24 = true; } if(offset == end) { return; } // If there is a timezone offset if(date.charAt(offset) == '+' || date.charAt(offset) == '-' || date.charAt(offset) == 'Z') { parseTimezone(date.substring(offset)); // Don't remove the character when calling return; } if(date.charAt(offset) != ':') { throw new GedcomxDateException("Invalid Date: Invalid Hour-Minute Separator"); } if(end-offset < 3) { throw new GedcomxDateException("Invalid Date: Minutes must be 2 digits"); } offset++; num = ""; for(int i=0;i<2;i++) { if(!Character.isDigit(date.charAt(offset))) { throw new GedcomxDateException("Invalid Date: Malformed Minutes"); } num += date.charAt(offset++); } minutes = Integer.valueOf(num); if(minutes > 59) { throw new GedcomxDateException("Invalid Date: Minutes must be between 0 and 59"); } if(flag24 && minutes != 0) { throw new GedcomxDateException("Invalid Date: Hours of 24 requires 00 Minutes"); } if(offset == end) { return; } // If there is a timezone offset if(date.charAt(offset) == '+' || date.charAt(offset) == '-' || date.charAt(offset) == 'Z') { parseTimezone(date.substring(offset)); // Don't remove the character when calling return; } if(date.charAt(offset) != ':') { throw new GedcomxDateException("Invalid Date: Invalid Minute-Second Separator"); } if(end-offset < 3) { throw new GedcomxDateException("Invalid Date: Seconds must be 2 digits"); } offset++; num = ""; for(int i=0;i<2;i++) { if(!Character.isDigit(date.charAt(offset))) { throw new GedcomxDateException("Invalid Date: Malformed Seconds"); } num += date.charAt(offset++); } seconds = Integer.valueOf(num); if(seconds > 59) { throw new GedcomxDateException("Invalid Date: Seconds must be between 0 and 59"); } if(flag24 && seconds != 0) { throw new GedcomxDateException("Invalid Date: Hours of 24 requires 00 Seconds"); } if(offset == end) { return; } else { parseTimezone(date.substring(offset)); // Don't remove the character when calling } } /** * Parse the timezone portion of the formal string * @param date The date string (minus the date and time) */ private void parseTimezone(String date) { int offset = 0; int end = date.length(); String num; // If Z we're done if(date.charAt(offset) == 'Z') { if(end == 1) { tzHours = 0; tzMinutes = 0; return; } else { throw new GedcomxDateException("Invalid Date: Malformed Timezone - No Characters allowed after Z"); } } if(end-offset < 3) { throw new GedcomxDateException("Invalid Date: Malformed Timezone - tzHours must be [+-] followed by 2 digits"); } // Must start with a + or - if(date.charAt(offset) != '+' && date.charAt(offset) != '-') { throw new GedcomxDateException("Invalid Date: TimeZone Hours must begin with + or -"); } offset++; num = date.charAt(0) == '-' ? "-" : ""; for(int i=0;i<2;i++) { if(!Character.isDigit(date.charAt(offset))) { throw new GedcomxDateException("Invalid Date: Malformed tzHours"); } num += date.charAt(offset++); } tzHours = Integer.valueOf(num); // Set tzMinutes to clear out default local tz offset tzMinutes = 0; if(offset == end) { return; } if(date.charAt(offset) != ':') { throw new GedcomxDateException("Invalid Date: Invalid tzHour-tzMinute Separator"); } if(end-offset < 3) { throw new GedcomxDateException("Invalid Date: tzSecond must be 2 digits"); } offset++; num = ""; for(int i=0;i<2;i++) { if(!Character.isDigit(date.charAt(offset))) { throw new GedcomxDateException("Invalid Date: Malformed tzMinutes"); } num += date.charAt(offset++); } tzMinutes = Integer.valueOf(num); if(offset == end) { return; } else { throw new GedcomxDateException("Invalid Date: Malformed Timezone - No characters allowed after tzSeconds"); } } /** * Get the Date Type * @return The type */ @Override public GedcomxDateType getType() { return GedcomxDateType.SIMPLE; } /** * Whether or not this date can be considered approximate * @return True if this is approximate */ @Override public boolean isApproximate() { return false; } /** * Output the formal string for this date * @return The formal date string */ @Override public String toFormalString() { StringBuilder simple = new StringBuilder(); simple.append(year >= 0 ? "+" : "-").append(String.format("%04d", Math.abs(year))); if(month != null) { simple.append("-").append(String.format("%02d", month)); } if(day != null) { simple.append("-").append(String.format("%02d", day)); } if(hours != null) { simple.append("T").append(String.format("%02d", hours)); if(minutes != null) { simple.append(":").append(String.format("%02d", minutes)); } if(seconds != null) { simple.append(":").append(String.format("%02d", seconds)); } // If we have time we always have tz if(tzHours == 0 && tzMinutes == 0) { simple.append("Z"); } else { simple.append(tzHours >= 0 ? "+" : "-").append(String.format("%02d", Math.abs(tzHours))); simple.append(":").append(String.format("%02d", tzMinutes)); } } return simple.toString(); } /** * Get the year * @return The Year */ public Integer getYear() { return year; } /** * Get the month * @return The Month */ public Integer getMonth() { return month; } /** * Get the day * @return The Day */ public Integer getDay() { return day; } /** * Get the hours * @return The Hours */ public Integer getHours() { return hours; } /** * Get the minutes * @return The Minutes */ public Integer getMinutes() { return minutes; } /** * Get the seconds * @return The seconds */ public Integer getSeconds() { return seconds; } /** * Get the timezone hours * @return The Timezone Hours */ public Integer getTzHours() { return tzHours; } /** * Get the timezone minutes * @return The Timezone Minutes */ public Integer getTzMinutes() { return tzMinutes; } }