/* * ----------------------------------------------------------------------- * Copyright © 2013-2015 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (DaytimeClock.java) is part of project Time4J. * * Time4J is free software: You can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * Time4J is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Time4J. If not, see <http://www.gnu.org/licenses/>. * ----------------------------------------------------------------------- */ package net.time4j.clock; import net.time4j.Moment; import net.time4j.PlainDate; import net.time4j.PlainTime; import net.time4j.format.Attributes; import net.time4j.format.expert.ChronoFormatter; import net.time4j.format.expert.ChronoParser; import net.time4j.format.expert.ParseLog; import net.time4j.format.expert.PatternType; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.Socket; import java.text.ParseException; import java.util.Locale; /** * <p>Represents a connection to a DAYTIME-server following the old norm * RFC 867. </p> * * <p>Note that the format of the server reply is not specified by the * protocol. Furthermore, many internet time servers have stopped to * support this old protocol. Actually in year 2014, at least the * addresses "time.nist.gov" and "time.ien.it" * are still working. Applications cannot expect more than second * precision at best. </p> * * @author Meno Hochschild * @since 2.1 * @doctags.concurrency {threadsafe} */ /*[deutsch] * <p>Repräsentiert eine Verbindung zu einem DAYTIME-Server nach der * alten Norm RFC 867. </p> * * <p>Hinweis: Das Format der Server-Antwort ist ein unspezifizierter String. * Viele Uhrzeit-Server bieten inzwischen keine Unterstützung mehr. * Aktuell im Jahr 2014 funktionieren wenigstens noch die Adressen * "time.nist.gov" und "time.ien.it". Mehr als * Sekundengenauigkeit ist nicht zu erwarten. </p> * * @author Meno Hochschild * @since 2.1 * @doctags.concurrency {threadsafe} */ public class DaytimeClock extends NetTimeConnector<NetTimeConfiguration> { //~ Statische Felder/Initialisierungen -------------------------------- private static final ChronoParser<PlainDate> MJD_PARSER = ChronoFormatter.setUp(PlainDate.class, Locale.ROOT).addPattern("ggggg", PatternType.CLDR).build(); private static final ChronoParser<PlainTime> TIME_PARSER = ChronoFormatter.setUp(PlainTime.class, Locale.ROOT).addPattern("HH:mm:ss", PatternType.CLDR).build(); private static final ChronoParser<Moment> NIST_PARSER = (text, status, attrs) -> { int pos = 0; while (!Character.isDigit(text.charAt(pos))) { pos++; } PlainDate date = MJD_PARSER.parse( text.subSequence(pos, 5 + pos), status, attrs); if (date != null) { status.setPosition(0); PlainTime time = TIME_PARSER.parse( text.subSequence(15 + pos, 23 + pos), status, attrs); if (time != null) { // leapsecond will be set to 59 in smart mode return date.at(time).atUTC(); } } return null; }; /** * <p>Defines an instance which supports the NIST-servers using * the multiple-location-address "time.nist.gov" and * a specific format. </p> * * <p>The format is documented at * <a href="http://www.nist.gov/">www.nist.gov</a>. </p> */ /*[deutsch] * <p>Definiert eine Instanz, die die NIST-Server mit der allgemeinen * Adresse "time.nist.gov" und deren spezifisches * Format verwendet. </p> * * <p>Das Format ist auf der Webseite * <a href="http://www.nist.gov/">www.nist.gov</a> dokumentiert. </p> */ public static final DaytimeClock NIST = new DaytimeClock("time.nist.gov", NIST_PARSER); //~ Instanzvariablen -------------------------------------------------- private final ChronoParser<Moment> parser; //~ Konstruktoren ----------------------------------------------------- /** * <p>Creates a new instance which uses the given time server on the * port 13. </p> * * @param server time server address (example: "time.nist.gov") * @param parser object interpreting the server reply */ /*[deutsch] * <p>Erzeugt eine neue Instanz, die den angegebenen Uhrzeit-Server * auf Port 13 benutzt. </p> * * @param server time server address (example: "time.nist.gov") * @param parser object interpreting the server reply */ public DaytimeClock( String server, ChronoParser<Moment> parser ) { super(new SimpleDaytimeConfiguration(server)); if (parser == null) { throw new NullPointerException( "Missing parser for translating any server reply."); } this.parser = parser; } //~ Methoden ---------------------------------------------------------- /** * <p>Tries to get the raw server timestamp as original string. </p> * * @return unparsed server reply * @throws IOException if connection fails * @since 2.1 */ /*[deutsch] * <p>Versucht, den Original-Server-Zeitstempel zu lesen. </p> * * @return unparsed server reply * @throws IOException if connection fails * @since 2.1 */ public String getRawTimestamp() throws IOException { final NetTimeConfiguration config = this.getNetTimeConfiguration(); String address = config.getTimeServerAddress(); int port = config.getTimeServerPort(); int timeout = config.getConnectionTimeout(); return getDaytimeReply(address, port, timeout); } @Override protected Moment doConnect() throws ParseException, IOException { final NetTimeConfiguration config = this.getNetTimeConfiguration(); String address = config.getTimeServerAddress(); int port = config.getTimeServerPort(); int timeout = config.getConnectionTimeout(); String time = getDaytimeReply(address, port, timeout); this.log("DAYTIME-Server connected: ", time); return this.parser.parse(time, new ParseLog(), Attributes.empty()); } @Override protected Class<NetTimeConfiguration> getConfigurationType() { return NetTimeConfiguration.class; } // Original-Antwort eines älteren Uhrzeit-Servers holen (RFC 867) private static String getDaytimeReply( String address, int port, int timeout ) throws IOException { IOException ioe = null; Socket socket = null; StringBuilder sb = null; try { socket = new Socket(address, port); socket.setSoTimeout(timeout * 1000); InputStream is = socket.getInputStream(); InputStreamReader ir = new InputStreamReader(is); BufferedReader br = new BufferedReader(ir); int len; char[] chars = new char[100]; sb = new StringBuilder(); while ((len = br.read(chars)) != -1) { sb.append(chars, 0, len); } is.close(); ir.close(); br.close(); } catch (IOException ex) { ioe = ex; } finally { try { if (socket != null) { socket.close(); } } catch (IOException ex) { System.err.println( "Ignored exception while closing time server socket: " + ex.getMessage() ); ex.printStackTrace(System.err); } } if (ioe == null) { return sb.toString(); } else { throw ioe; } } //~ Innere Klassen ---------------------------------------------------- private static class SimpleDaytimeConfiguration implements NetTimeConfiguration { //~ Instanzvariablen ---------------------------------------------- private final String server; //~ Konstruktoren ------------------------------------------------- SimpleDaytimeConfiguration(String server) { super(); if (server == null) { throw new NullPointerException("Missing time server address."); } this.server = server; } //~ Methoden ------------------------------------------------------ @Override public String getTimeServerAddress() { return this.server; } @Override public int getTimeServerPort() { return 13; } @Override public int getConnectionTimeout() { return DEFAULT_CONNECTION_TIMEOUT; } @Override public int getClockShiftWindow() { return 0; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("SimpleDaytimeConfiguration:[server="); sb.append(this.server); sb.append(",port="); sb.append(this.getTimeServerPort()); sb.append(']'); return sb.toString(); } } }