@@ -143,6 +143,13 @@ public class StdDateFormat
143
143
144
144
private transient DateFormat _formatRFC1123 ;
145
145
146
+ /**
147
+ * Whether the TZ offset must be formatted with a colon between hours and minutes ({@code HH:mm} format)
148
+ *
149
+ * @since 2.9.1
150
+ */
151
+ private boolean _tzSerializedWithColon = false ;
152
+
146
153
/*
147
154
/**********************************************************
148
155
/* Life cycle, accessing singleton "standard" formats
@@ -154,9 +161,18 @@ public StdDateFormat() {
154
161
}
155
162
156
163
protected StdDateFormat (TimeZone tz , Locale loc , Boolean lenient ) {
164
+ this (tz , loc , lenient , false );
165
+ }
166
+
167
+ /**
168
+ * @since 2.9.1
169
+ */
170
+ protected StdDateFormat (TimeZone tz , Locale loc , Boolean lenient ,
171
+ boolean formatTzOffsetWithColon ) {
157
172
_timezone = tz ;
158
173
_locale = loc ;
159
174
_lenient = lenient ;
175
+ _tzSerializedWithColon = formatTzOffsetWithColon ;
160
176
}
161
177
162
178
public static TimeZone getDefaultTimeZone () {
@@ -174,31 +190,61 @@ public StdDateFormat withTimeZone(TimeZone tz) {
174
190
if ((tz == _timezone ) || tz .equals (_timezone )) {
175
191
return this ;
176
192
}
177
- return new StdDateFormat (tz , _locale , _lenient );
193
+ return new StdDateFormat (tz , _locale , _lenient , _tzSerializedWithColon );
178
194
}
179
195
196
+ /**
197
+ * "Mutant factory" method that will return an instance that uses specified
198
+ * {@code Locale}:
199
+ * either {@code this} instance (if setting would not change), or newly
200
+ * constructed instance with different {@code Locale} to use.
201
+ */
180
202
public StdDateFormat withLocale (Locale loc ) {
181
203
if (loc .equals (_locale )) {
182
204
return this ;
183
205
}
184
- return new StdDateFormat (_timezone , loc , _lenient );
206
+ return new StdDateFormat (_timezone , loc , _lenient , _tzSerializedWithColon );
185
207
}
186
208
187
209
/**
210
+ * "Mutant factory" method that will return an instance that has specified leniency
211
+ * setting: either {@code this} instance (if setting would not change), or newly
212
+ * constructed instance.
213
+ *
188
214
* @since 2.9
189
215
*/
190
216
public StdDateFormat withLenient (Boolean b ) {
191
217
if (_equals (b , _lenient )) {
192
218
return this ;
193
219
}
194
- return new StdDateFormat (_timezone , _locale , b );
220
+ return new StdDateFormat (_timezone , _locale , b , _tzSerializedWithColon );
195
221
}
196
222
223
+ /**
224
+ * "Mutant factory" method that will return an instance that has specified
225
+ * handling of colon when serializing timezone (timezone either written
226
+ * like {@code +0500} or {@code +05:00}):
227
+ * either {@code this} instance (if setting would not change), or newly
228
+ * constructed instance with desired setting for colon inclusion.
229
+ *<p>
230
+ * NOTE: does NOT affect deserialization as colon is optional accepted
231
+ * but not required -- put another way, either serialization is accepted
232
+ * by this class.
233
+ *
234
+ * @since 2.9.1
235
+ */
236
+ public StdDateFormat withColonInTimeZone (boolean b ) {
237
+ if (_tzSerializedWithColon == b ) {
238
+ return this ;
239
+ }
240
+ return new StdDateFormat (_timezone , _locale , _lenient , b );
241
+ }
242
+
197
243
@ Override
198
244
public StdDateFormat clone () {
199
245
// Although there is that much state to share, we do need to
200
246
// orchestrate a bit, mostly since timezones may be changed
201
- return new StdDateFormat (_timezone , _locale , _lenient );
247
+ return new StdDateFormat (_timezone , _locale , _lenient , _tzSerializedWithColon );
202
248
}
203
249
204
250
/*
@@ -245,6 +291,24 @@ public boolean isLenient() {
245
291
return (_lenient == null ) || _lenient .booleanValue ();
246
292
}
247
293
294
+ /**
295
+ * Accessor for checking whether this instance would include colon
296
+ * within timezone serialization or not: if {code true}, timezone offset
297
+ * is serialized like {@code -06:00}; if {code false} as {@code -0600}.
298
+ *<p>
299
+ * NOTE: only relevant for serialization (formatting), as deserialization
300
+ * (parsing) always accepts optional colon but does not require it, regardless
301
+ * of this setting.
302
+ *
303
+ * @return {@code true} if a colon is to be inserted between the hours and minutes
304
+ * of the TZ offset when serializing as String; otherwise {@code false}
305
+ *
306
+ * @since 2.9.1
307
+ */
308
+ public boolean isColonIncludedInTimeZone () {
309
+ return _tzSerializedWithColon ;
310
+ }
311
+
248
312
/*
249
313
/**********************************************************
250
314
/* Public API, parsing
@@ -363,15 +427,20 @@ protected void _format(TimeZone tz, Locale loc, Date date,
363
427
int minutes = Math .abs ((offset / (60 * 1000 )) % 60 );
364
428
buffer .append (offset < 0 ? '-' : '+' );
365
429
pad2 (buffer , hours );
366
- // 24-Jun-2017, tatu: To add colon or not to add colon? Both are legal...
367
- // tests appear to expect no colon so let's go with that.
368
- // formatted.append(':');
430
+ if ( _tzSerializedWithColon ) {
431
+ buffer . append ( ':' );
432
+ }
369
433
pad2 (buffer , minutes );
370
434
} else {
371
435
// 24-Jun-2017, tatu: While `Z` would be conveniently short, older specs
372
436
// mandate use of full `+0000`
373
437
// formatted.append('Z');
374
- buffer .append ("+0000" );
438
+ if ( _tzSerializedWithColon ) {
439
+ buffer .append ("+00:00" );
440
+ }
441
+ else {
442
+ buffer .append ("+0000" );
443
+ }
375
444
}
376
445
}
377
446
0 commit comments