@@ -356,6 +356,7 @@ where
356
356
}
357
357
358
358
/// Use the local time zone for the timestamp (default)
359
+ #[ cfg( feature = "localtime" ) ]
359
360
pub fn use_local_timestamp ( mut self ) -> Self {
360
361
self . fn_timestamp = Box :: new ( timestamp_local) ;
361
362
self
@@ -1089,18 +1090,53 @@ where
1089
1090
{
1090
1091
}
1091
1092
1092
- const TIMESTAMP_FORMAT : & [ time:: format_description:: FormatItem ] = time:: macros:: format_description!( "[month repr:short] [day] [hour repr:24]:[minute]:[second].[subsecond digits:3]" ) ;
1093
+ const TIMESTAMP_FORMAT : & [ time:: format_description:: FormatItem ] = time:: macros:: format_description!(
1094
+ concat!(
1095
+ "[month repr:short] [day]" ,
1096
+ "[hour repr:24]:[minute]:[second].[subsecond digits:3]" ,
1097
+ // Include timezone by default, to avoid ambiguity
1098
+ "[offset_hour sign:mandatory]"
1099
+ )
1100
+ ) ;
1101
+
1102
+ /// Convert from [`chrono::DateTime`] into [`time::OffsetDateTime`]
1103
+ #[ cfg( feature = "localtime" ) ]
1104
+ fn local_timestamp_from_chrono (
1105
+ local : chrono:: DateTime < chrono:: Local >
1106
+ ) -> result:: Result < time:: OffsetDateTime , TimestampError > {
1107
+ use chrono:: { Local , Utc } ;
1108
+ let local_time: chrono:: DateTime < Local > = Local :: now ( ) ;
1109
+ let offset: chrono:: FixedOffset = local_time. fixed_offset ( ) . timezone ( ) ;
1110
+ let utc_time: chrono:: DateTime < Utc > = local_time. to_utc ( ) ;
1111
+ #[ cfg( test) ] {
1112
+ if offset. local_minus_utc ( ) == 0 {
1113
+ assert_eq ! ( utc_time. date_naive( ) , local_time. date_naive( ) ) ;
1114
+ } else {
1115
+ assert_ne ! ( utc_time. date_naive( ) , local_time. date_naive( ) ) ;
1116
+ }
1117
+ }
1118
+ let utc_time: time:: OffsetDateTime = time:: OffsetDateTime :: from_unix_timestamp ( utc_time. timestamp ( ) )
1119
+ . map_err ( LocalTimestampError :: UnixTimestamp )
1120
+ + time:: Duration :: nanoseconds ( i64:: from ( utc_time. timestamp_subsec_nanos ( ) ) ) ;
1121
+ Ok ( utc_time. to_offset ( time:: UtcOffset :: from_whole_seconds ( offset. local_minus_utc ( ) ) ?) )
1122
+ }
1093
1123
1094
1124
/// Default local timezone timestamp function
1095
1125
///
1096
1126
/// The exact format used, is still subject to change.
1127
+ ///
1128
+ /// # Implementation Note
1129
+ /// This requires `chrono` to detect the localtime in a thread-safe manner.
1130
+ /// See the comment on the feature flag and PR #44
1131
+ #[ cfg( feature = "localtime" ) ]
1097
1132
pub fn timestamp_local ( io : & mut dyn io:: Write ) -> io:: Result < ( ) > {
1098
- let now: time:: OffsetDateTime = std:: time:: SystemTime :: now ( ) . into ( ) ;
1133
+ let now = local_timestamp_from_chrono ( chrono:: Local :: now ( ) )
1134
+ . map_err ( TimestampError :: LocalConversion ) ?;
1099
1135
write ! (
1100
1136
io,
1101
1137
"{}" ,
1102
1138
now. format( TIMESTAMP_FORMAT )
1103
- . map_err( convert_time_fmt_error ) ?
1139
+ . map_err( TimestampError :: Format ) ?
1104
1140
)
1105
1141
}
1106
1142
@@ -1113,11 +1149,44 @@ pub fn timestamp_utc(io: &mut dyn io::Write) -> io::Result<()> {
1113
1149
io,
1114
1150
"{}" ,
1115
1151
now. format( TIMESTAMP_FORMAT )
1116
- . map_err( convert_time_fmt_error ) ?
1152
+ . map_err( TimestampError :: Format ) ?
1117
1153
)
1118
1154
}
1119
- fn convert_time_fmt_error ( cause : time:: error:: Format ) -> io:: Error {
1120
- io:: Error :: new ( io:: ErrorKind :: Other , cause)
1155
+
1156
+ /// An internal error that occurs with timestamps
1157
+ #[ derive( Debug ) ]
1158
+ #[ non_exhaustive]
1159
+ enum TimestampError {
1160
+ /// An error formatting the timestamp
1161
+ Format ( time:: error:: Format ) ,
1162
+ /// An error that occurred while
1163
+ /// converting from `chrono::DateTime<Local>` to `time::OffsetDateTime`
1164
+ LocalTimeConversion ( time:: error:: ComponentRange )
1165
+ }
1166
+ /*
1167
+ * We could use thiserror here,
1168
+ * but writing it by hand is almost as good and eliminates a dependency
1169
+ */
1170
+ impl fmt:: Display for TimestampError {
1171
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
1172
+ match self {
1173
+ TimestampError :: Format ( cause) => write ! ( f, "Failed to format timestamp: {cause}" ) ,
1174
+ TimestampError :: LocalTimeConversion ( cause) => write ! ( f, "Failed to convert local time: {cause}" )
1175
+ }
1176
+ }
1177
+ }
1178
+ impl From < TimestampError > for io:: Error {
1179
+ fn from ( cause : TimestampError ) -> Self {
1180
+ io:: Error :: new ( io:: ErrorKind :: Other , cause)
1181
+ }
1182
+ }
1183
+ impl std:: error:: Error for TimestampError {
1184
+ fn source ( & self ) -> Option < & ( dyn std:: error:: Error + ' static ) > {
1185
+ match self {
1186
+ TimestampError :: Format ( cause) => Some ( cause) ,
1187
+ TimestampError :: LocalTimeConversion ( cause) => Some ( cause) ,
1188
+ }
1189
+ }
1121
1190
}
1122
1191
1123
1192
// }}}
0 commit comments