awips2/edexOsgi/com.raytheon.uf.common.wmo/src/com/raytheon/uf/common/wmo/WMOTimeParser.java
2022-05-05 12:34:50 -05:00

432 lines
16 KiB
Java

/**
* This software was developed and / or modified by Raytheon Company,
* pursuant to Contract DG133W-05-CQ-1067 with the US Government.
*
* U.S. EXPORT CONTROLLED TECHNICAL DATA
* This software product contains export-restricted data whose
* export/transfer/disclosure is restricted by U.S. law. Dissemination
* to non-U.S. persons whether in the United States or abroad requires
* an export license or other authorization.
*
* Contractor Name: Raytheon Company
* Contractor Address: 6825 Pine Street, Suite 340
* Mail Stop B8
* Omaha, NE 68106
* 402.291.0100
*
* See the AWIPS II Master Rights File ("Master Rights File.pdf") for
* further licensing information.
**/
package com.raytheon.uf.common.wmo;
import java.text.ParseException;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.DataFormatException;
import com.raytheon.uf.common.status.IUFStatusHandler;
import com.raytheon.uf.common.status.UFStatus;
import com.raytheon.uf.common.status.UFStatus.Priority;
import com.raytheon.uf.common.time.util.TimeUtil;
/**
* Static utilities for parsing dates/times from WMO products
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* May 13, 2014 2536 bclement Initial creation, moved from TimeTools
* Nov 10, 2014 3549 njensen Improve logging of findDataTime()
* Mar 24, 2016 5501 tgurney Add getAdjustedRefTime()
* Jan 16, 2018 17469 wkwock Change adjustDayHourMinute() visibility to public
*
* </pre>
*
* @author bclement
* @version 1.0
*/
public class WMOTimeParser {
private static final IUFStatusHandler logger = UFStatus
.getHandler(WMOTimeParser.class);
/** Environment variable to indicate archived files. */
private static final String ALLOW_ARCHIVE_ENV = "ALLOW_ARCHIVE_DATA";
/**
* Time stamp that includes the receipt time format at the end of the file
* name: .YYYYMMDD or .YYYYMMDDHH
*/
private static final Pattern FILE_TIMESTAMP = Pattern
.compile("(.*\\.)(\\d{8}|\\d{10})$");
public static final Pattern WMO_TIMESTAMP = Pattern
.compile("([0-3][0-9])(\\d{2})(\\d{2})[Zz]?");
/**
* Get a calendar that expresses the current system time based on specified
* date information or the current service time if not allowing archive.
*
* Note that the month argument should be 1 based (e.g. January = 1) and the
* returned calendar will have the month be 0 based (e.g. January = 0).
*
* @param year
* Year to set.
* @param month
* @param day
* @param hour
* @param minute
* @return The current time as a GMT Calendar.
*/
private static final Calendar getSystemCalendar(int year, int month,
int day, int hour, int minute) {
Calendar retCal = TimeUtil.newGmtCalendar();
if (allowArchive()) {
if (isValidDate(year, month, day)) {
if (hour != -1) {
if (minute != -1) {
retCal.set(Calendar.YEAR, year);
retCal.set(Calendar.MONTH, month - 1);
retCal.set(Calendar.DAY_OF_MONTH, day);
retCal.set(Calendar.HOUR_OF_DAY, hour);
retCal.set(Calendar.MINUTE, minute);
retCal.set(Calendar.SECOND, 0);
retCal.set(Calendar.MILLISECOND, 0);
}
}
}
}
return retCal;
}
/**
* Get Calendar with the time based on the timestamp at the end of the
* fileName.
*
* @param fileName
* @return calendar
*/
public static final Calendar getSystemCalendar(String fileName) {
int year = -1;
int month = -1;
int day = -1;
int hour = -1;
if (fileName != null) {
Matcher matcher = FILE_TIMESTAMP.matcher(fileName);
if (matcher.find()) {
String yyyymmdd = matcher.group(2);
try {
year = Integer.parseInt(yyyymmdd.substring(0, 4));
month = Integer.parseInt(yyyymmdd.substring(4, 6));
day = Integer.parseInt(yyyymmdd.substring(6, 8));
if (yyyymmdd.length() < 10) {
hour = 0;
} else {
hour = Integer.parseInt(yyyymmdd.substring(8, 10));
}
} catch (NumberFormatException nfe) {
year = -1;
month = -1;
day = -1;
}
}
}
return getSystemCalendar(year, month, day, hour, 0);
}
/**
* Get the timestamp in the file name.
*
* @param fileName
* @return timestamp if it matches FILE_TIMESTAMP otherwise null
*/
public static final String getTimestamp(String fileName) {
String timestamp = null;
Matcher matcher = FILE_TIMESTAMP.matcher(fileName);
if (matcher.find()) {
timestamp = matcher.group(2);
}
return timestamp;
}
/**
* Converts a ddhhmm time group to a Calendar. Adjusts the calendar as
* follows: Any time group with a day (dd) in the future is set back one
* month.
*
* @param wmoDateStamp
* the time to convert
*
* @return the converted time
*
* @throws DataFormatException
* if an error occurs
*/
public static final Calendar findCurrentTime(String wmoDateStamp,
String fileName) throws DataFormatException {
Calendar refCal = getSystemCalendar(fileName);
try {
Matcher matcher = WMO_TIMESTAMP.matcher(wmoDateStamp);
if (matcher.matches()) {
int iDay = Integer.parseInt(matcher.group(1));
int iHour = Integer.parseInt(matcher.group(2));
int iMinute = Integer.parseInt(matcher.group(3));
refCal = adjustDayHourMinute(refCal, iDay, iHour, iMinute);
} else {
throw new ParseException("Invalid format - time does not match "
+ WMO_TIMESTAMP.pattern(), 0);
}
} catch (Exception e) {
throw new DataFormatException("Unable to find current time for "
+ wmoDateStamp + ", exception was " + e.toString());
}
return refCal;
}
/**
* Convert a string in ddhhmm format to a standard {@link Calendar} format
* where ddhhmm is the GMT format while the standard time is in Calendar
* format with Year and Month information. Usage: ddhhmm is the issue time
* whereas utcTime can be the MDN time. The former comes "after" the latter.
*
* @parm ddhhmm day-hour-minute in GMT
* @parm local Time UTC time in Calendar
*/
public static final Calendar findDataTime(String ddhhmm, String fileName) {
Calendar issueTime = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
try {
issueTime = findCurrentTime(ddhhmm, fileName);
} catch (DataFormatException e) {
logger.handle(Priority.WARN,
"Error in processing MND time; return current time", e);
}
return issueTime;
}
/**
* Adjusts the calendar from the current date to the specified date. If the
* specified date is later than the current date, the calendar is "backed
* up" one month. In addition, the second and millisecond fields are set to
* zero.
*
* @param cal
* the calendar to adjust
* @param day
* the new day of month
* @param hour
* the new hour of day
* @param minute
* the new minute of the hour
*/
public static Calendar adjustDayHourMinute(Calendar cal, int wmoDay,
int wmoHour, int wmoMinute) {
if (cal != null) {
int cDay = cal.get(Calendar.DAY_OF_MONTH);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
// Range check hour/minute first. Have to wait for
// checking the day
if (isValidHour(wmoHour) && (isValidMinSec(wmoMinute))) {
Calendar lastMonth = (Calendar) cal.clone();
lastMonth.set(Calendar.DAY_OF_MONTH, 1);
lastMonth.add(Calendar.MONTH, -1);
// Get the maximum day of the current month from the reference
// calendar
int maxDayThisMonth = cal
.getActualMaximum(Calendar.DAY_OF_MONTH);
// Set the day to one so all add/subtracts work correctly
cal.set(Calendar.DAY_OF_MONTH, 1);
cal.set(Calendar.HOUR_OF_DAY, wmoHour);
cal.set(Calendar.MINUTE, wmoMinute);
if (wmoDay == 1) {
// the wmoDay is 1
// and the reference calendar is the last
// day of the month
if (cDay == maxDayThisMonth) {
// This is potentially next month's data received early
// Allow three hours into the next day
if (wmoHour < 3) {
// Advance to the next month
cal.add(Calendar.MONTH, 1);
// and set the hour, minute
}
}
} else if (wmoDay > cDay) {
// Is the wmoDay valid for this month?
if (wmoDay <= maxDayThisMonth) {
// First allow up to 3 hours into the next day
if ((cDay + 1) == wmoDay) {
// This is potentially next month's data received
// early. Allow three hours into the next day
if (wmoHour > 2) {
// Back up a month
cal.add(Calendar.MONTH, -1);
}
} else {
// Back up a month
cal.add(Calendar.MONTH, -1);
int mDay = cal
.getActualMaximum(Calendar.DAY_OF_MONTH);
if (mDay < wmoDay) {
cal.add(Calendar.MONTH, -1);
}
}
} else {
// The wmoDay is greater than the maximum number
// of days for the reference month. We can't back
// up one month, but can always back up two months.
cal.add(Calendar.MONTH, -2);
}
}
cal.set(Calendar.DAY_OF_MONTH, wmoDay);
} else {
// bad
cal = null;
}
}
return cal;
}
/**
* Convert ref time in ddhhmm format to a Calendar, with year and month set
* based on the provided WMO time.
*
* The returned ref time will have the same year and month as the provided
* WMO time, except in the special case where the WMO time falls on the
* first day of the month, and the ref time falls on the last day of the
* previous month. In this case, the year/month of the ref time will be set
* one month back from that of the WMO time. e.g., if WMO date is 2016-01-01
* and ref time is given as 312355, then returned ref time is 2015-12-31
* 23:55.
*
* @param ddhhmm
* Ref time in ddhhmm format (with or without 'Z' suffix)
*
* @param wmoHeaderTime
* Time from WMO header, with year and month set in addition to
* day/hour/minute.
*
* @return Ref time with year and month set in addition to day/hour/minute,
* and seconds/ms set to zero. Null if the ddhhmm arg does not
* correspond to a valid ref time.
*/
public static Calendar getAdjustedRefTime(String ddhhmm,
Calendar wmoHeaderTime) {
Calendar refTime = null;
Matcher matcher = WMO_TIMESTAMP.matcher(ddhhmm);
if (matcher.matches()) {
int refYear = wmoHeaderTime.get(Calendar.YEAR);
int refMonth = wmoHeaderTime.get(Calendar.MONTH);
int refDay = Integer.parseInt(matcher.group(1));
int refHour = Integer.parseInt(matcher.group(2));
int refMinute = Integer.parseInt(matcher.group(3));
if (wmoHeaderTime.get(Calendar.DAY_OF_MONTH) == 1) {
Calendar cal = (Calendar) wmoHeaderTime.clone();
cal.set(Calendar.DAY_OF_MONTH, 1);
cal.add(Calendar.DAY_OF_MONTH, -1);
int lastDayPrevMonth = cal.get(Calendar.DAY_OF_MONTH);
if (refDay == lastDayPrevMonth) {
refYear = cal.get(Calendar.YEAR);
refMonth = cal.get(Calendar.MONTH);
}
}
refTime = (Calendar) wmoHeaderTime.clone();
refTime.set(refYear, refMonth, refDay, refHour, refMinute, 0);
refTime.set(Calendar.MILLISECOND, 0);
}
return refTime;
}
/**
* Is a specified date valid? This method checks an entire year, month, day
* timestamp.
*
* @param year
* The year to check.
* @param month
* Numeric value of the month.
* @param day
* Is the month valid?
* @return Is year, month, day timestamp valid.
*/
private static final boolean isValidDate(int year, int month, int day) {
boolean validDay = false;
if (day > -1) {
if (isValidYear(year)) {
if (isValidMonth(month)) {
Calendar c = TimeUtil.newGmtCalendar(year, month, 1);
int lastDay = c.getActualMaximum(Calendar.DAY_OF_MONTH) + 1;
validDay = (day < lastDay);
}
}
}
return validDay;
}
/**
* Is the year valid. This method supposes any positive year value as valid.
*
* @param year
* The year to check.
* @return Is the year valid?
*/
private static final boolean isValidYear(int year) {
return (year >= 0);
}
/**
* The the specified month of the year valid.
*
* @param month
* Numeric value of the month.
* @return Is the month valid?
*/
private static final boolean isValidMonth(int month) {
return ((month > 0) && (month <= 12));
}
/**
* Is the specified hour of the day valid? Range 0..23 inclusive.
*
* @param hour
* The hour to check.
* @return Is the hour valid?
*/
private static final boolean isValidHour(int hour) {
return ((hour > -1) && (hour < TimeUtil.HOURS_PER_DAY));
}
/**
* Is the specified minute/second valid? Range 0..59 inclusive.
*
* @param hour
* The minute/second to check.
* @return Is the minute/second valid?
*/
private static final boolean isValidMinSec(int value) {
return ((value > -1) && (value < TimeUtil.MINUTES_PER_HOUR));
}
/**
* Check to see if archive files are allowed.
*
* @return true when archive files are allowed
*/
public static boolean allowArchive() {
// This doesn't pick up the environment variable.
// return Boolean.getBoolean(ALLOW_ARCHIVE_ENV);
return "true".equalsIgnoreCase(System.getenv().get(ALLOW_ARCHIVE_ENV));
}
}